因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构。
划重点
微服务架构主要特点:
- ==独立组件(自主开发升级)==
- ==开运一体(谁开发谁运维)==
- ==去中心化(语言,系统,数据,统统可以分开不一样)==
一. 那么什么是微服务架构呢?
引自 https://www.ibm.com/developerworks/community/blogs/
“微服务”架构是近期软件应用领域非常热门的概念。
微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
特点
也就是说,微服务就是SOA的变种,在保证SOA业务分离能力的同时,减少每个业务粒度,同时去除了SOA中比较恼人的ESB.
用最精辟的话说就是==独立部署==,==快速迭代==.
通用特性
- 组件化,并可独立替换和升级
- 业务分离,每个服务团队运维一体化
- 服务产品化而非项目模式(其实和上面差不多,就是谁开发,谁运营)
- 智能端点与管道扁平化(Smartendpoints and dumb pipes):微服务架构主张将组件间通讯的相关业务逻辑/智能放在组件端点侧而非放在通讯组件中,通讯机制或组件应该尽量简单及松耦合。RESTful HTTP协议和仅提供消息路由功能的轻量级异步机制是微服务架构中最常用的通讯机制。
- “去中心化”治理(DecentralizedGovernance):整体式应用往往倾向于采用单一技术平台,微服务架构则鼓励使用合适的工具完成各自的任务,每个微服务可以考虑选用最佳工具完成(如不同的编程语言)。微服务的技术标准倾向于寻找其他开发者已成功验证解决类似问题的技术。
- “去中心化”数据管理(DecentralizedData Management):微服务架构倡导采用多样性持久化(PolyglotPersistence)的方法,让每个微服务管理其自有数据库,并允许不同微服务采用不同的数据持久化技术。
- 基础设施自动化(InfrastructureAutomation):云化及自动化部署等技术极大地降低了微服务构建、部署和运维的难度,通过应用持续集成和持续交付等方法有助于达到加速推出市场的目的。
- 故障处理设计(Designfor failure):微服务架构所带来的一个后果是必须考虑每个服务的失败容错机制。因此,微服务非常重视建立架构及业务相关指标的实时监控和日志机制。
- 演进式的设计(EvolutionaryDesign):如某应用是整体式应用,但逐渐朝微应用架构方向演进,整体式应用仍是核心,但新功能将使用应用所提供的API构建。再如在某微服务应用中,可替代性模块化设计的基本原则,在实施后发现某两个微服务经常必须同时更新,则这很可能意味着应将其合并为一个微服务。
自由组装
这个图片从资源利用率的角度,体现了微服务一个非常给力的优点
数据分离
同时,对应用组件封装的方式是整体架构与微服务架构的主要差异,微服务架构将相关联的业务逻辑及数据放在一起形成独立的边界,其目的是能在不影响其他应用组件(微服务)的情况下更快地交付并推出市场。
概念比较
在同一范畴内比较才有意义:
- 微服务架构 vs. SOA – 两者都是架构风格范畴,但其关注领域与涉及范围不同。SOA更关注企业规模范围,微服务架构则更关注应用规模范围。
- 微服务组件 vs. 服务组件 – 两者都是描述业务功能的具体实现,其区别在于粒度不同,此外还有在可管理性、灵活性上的差异。
概念混淆的不恰当比较
- 微服务 vs. SOA – 不恰当的比较。微服务是组件范畴,而SOA是一种架构设计风格。因此应该比较的是微服务架构与SOA。
- 微服务 vs. API – 不恰当的比较。 API是接口,是业务功能暴露的一种机制。微服务架构是用于实施业务功能的组件架构。因此直接比较它们是没有意义的。
- 微服务 vs. 服务– 不恰当的比较。“服务”在不同的场景下有不同的含义,需要进一步澄清其描述的语境,是指服务实施、服务暴露、服务定义还是其他?微服务亦是如此,需要有特定语境才可判断比较是否有意义。
二. 前后对接协议 JsonApi
※. 本期语法糖
※.1 Scala的Package Object
官网讲解https://www.scala-lang.org/docu/files/packageobjects/packageobjects.html
为啥使用Package Object
而不是Object
呢?
原因很简单,答案是==代码更干净==。如何做到的呢?
我copy了官网的例子,诸位请看:
// in file gardening/fruits/Fruit.scala
package gardening.fruits
case class Fruit(name: String, color: String)
object apple extends Fruit("Apple", "green")
object plum extends Fruit("Plum", "blue")
object banana extends Fruit("Banana", "yellow")
上面我在/gardening/fruits/
包下定义了几个Object
如果想使用直接使用上面定义的几个Object,可以这样
package com
object main {
import gardening.fruits._
val planted = List(apple, plum, banana)
def showFruit(fruit: Fruit) {
println(fruit.name +"s are "+ fruit.color)
}
}
但是,其实我们还有更简洁的调用方式,各位也已经猜到了,就是Package Object
,
只要在调用包的上一级/gardening/
包下定义Package Object fruits
,这样的写法就和class和objcet的伴生关系一样,我们可以直接访问伴生包中的变量,方法和隐式。
// in file gardening/fruits/package.scala
package gardening
package object fruits {
val planted = List(apple, plum, banana)
def showFruit(fruit: Fruit) {
println(fruit.name +"s are "+ fruit.color)
}
}
这里要注意,思想不要局限,反过来也是可以的,比如在Package Object fruits
中定义了一系列的object,在/gardening/
包下的object可以直接使用。
※.2 Scala的自定义注解(Annotation)
※.2.1 Annotation (注解)的介绍
Scala中的注解语法与Java中类似。
标准库定义的注解相关内容在包scala.annotation中。
注解的基本语法为:
@注解名称(注解参数...)
val a = ???
与Java注解的用法类似,注解参数不是必须的,一个元素允许拥有多个注解。
※.2.2 Annotation的定义
==Scala中的自定义注解不是接口/特质,而是类.==
自定义注解需要从注解特质中继承,Scala中提供了两类注解特质:
- scala.annotation.ClassfileAnnotation 由Java编译器生成注解
- scala.annotation.StaticAnnotation 由Scala编译器生成注解
两类注解特质都继承自基类scala.annotation.Annotation.两类注解特质的基类相同,因此自定义注解类时允许同时混入两类注解特质。
- 继承自ClassfileAnnotation基类的注解在使用时参数应以具名参数(named arguments)形式传入。
- 继承自StaticAnnotation基类的注解无此限制。
定义注解类语法与普通类相同:
// 标记注解
class 注解名称 extends StaticAnnotation/ClassfileAnnotation
// 有参注解
class 注解名称(参数表...) extends StaticAnnotation/ClassfileAnnotation
※.2.3 Annotation的解析(表达式树)
通过反射机制获取注解信息,相关API位于scala.reflect.runtime.universe
包路径下。
获取注解:
- 获取类的注解:
- 使用typeOf()方法,获取Type类型的类信息。
typeOf[Test]: Type
- 通过Type.typeSymbol获取Symbol。
type.typeSymbol: Symbol
- 通过Symbol.annotations获取List[Annotation](注解列表)。
symbol.annotations.head: Annotation
- 根据注解列表的head获得语法树
annotation.tree: Tree
- 使用typeOf()方法,获取Type类型的类信息。
- 获取方法/成员字段的注解:
- 使用typeOf()方法,获取Type类型的类信息。
typeOf[Test]: Type
- 通过Type.decls/decl()方法筛选出目标成员的Symbol。
type.decl(TermName("ff ")): Symbol
- 通过Symbol.annotations获取List[Annotation](注解列表)。
symbol.annotations.head: Annotation
- 根据注解列表的head获得语法树
annotation.tree: Tree
- 使用typeOf()方法,获取Type类型的类信息。
- 获取方法参数的注解:
- 使用typeOf()方法,获取Type类型的类信息。
typeOf[Test]: Type
- 通过Type.decls/decl()方法筛选出目标方法的MethodSymbol。
type.decl(TermName("ff ")): MethodSymbol
通过MethodSymbol.paramLists方法获取目标方法的参数表(List[List[Symbol]]类型,方法柯里化可能会有多个参数表)。 - 通过Symbol.annotations获取List[Annotation](注解列表)。
methodSymbol.annotations.head: Annotation
- 使用typeOf()方法,获取Type类型的类信息。
解析注解Dome:
应使用Annotation.tree方法获取注解语法树,类型为scala.reflect.runtime.universe.Tree。
import scala.annotation.StaticAnnotation
import scala.reflect.runtime.universe._
class CustomAnnotation(name: String, num: Int) extends StaticAnnotation {
override def toString = s"Annotation args: name -> $name, num -> $num"
}
@CustomAnnotation("Annotation for Class", 2333)
class Test {
@CustomAnnotation("Annotation for Class", 6666)
val ff = ""
}
object Main extends App {
{
// 获取类型注解
val tpe: Type = typeOf[Test]
val symbol: Symbol = tpe.typeSymbol //获取类型符号信息
val annotation: Annotation = symbol.annotations.head
val tree: Tree = annotation.tree //获取语法树
// 解析注解语法树...
}
{
// 获取成员字段注解
val tpe: Type = typeOf[Test]
val symbol: Symbol = tpe.decl(TermName("ff ")) //获取字段符号信息
val annotation: Annotation = symbol.annotations.head
val tree: Tree = annotation.tree
// 解析注解语法树...
}
}
※.3 Scala的implicit用法
平时我们比较常见的应该是使用implicit
修饰val
或者def
,如果在函数的定义中,有如下函数签名
def divide(x: Int, y: Int)(implicit i2d: Int => Double): Double
则调用上述的divide
方法时必须显示或隐式的传入一个类型为Int=>Double
的函数映射.如:
divide(1, 2)(i => i.toDouble)
如果在当前名称空间中,正好有了这个Int => Double
的函数映射,并且是隐式的,如:
implicit val test: Int => Double = i => i.toDouble
则函数调用的时候,则可以省略后面的隐式传入,编译器会自动使用当前名称空间的可用隐式,调用就变为:
divide(1, 2)
Implicit class
那么问题来了,如果implicit修饰的是class呢?
其实,我们可以认为带有这个关键字的类的主构造函数可用于隐式转换。
举个例子, 创建隐式类时,只需要在对应的类前加上implicit关键字。比如:
// 定义
object Helpers {
implicit class IntWithTimes(x: Int) {
def times[A](f: => A): Unit = {
def loop(current: Int): Unit =
if(current > 0) {
f
loop(current - 1)
}
loop(x)
}
}
}
// 调用
object main {
import Helpers._
5 times println("HI")
}
可以发现, 5
是个Int型, 没有times方法,会自动查找名称空间,找到可以转换为IntWithTimes类型,并且带有times方法. 所以上面函数可以调用成功.
限制条件
- 使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。
- 只能在别的trait/类/对象内部定义。
- 构造函数只能携带一个非隐式参数。
- 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。(也就说明case class 不可被implicit修饰,因为自动生成伴生类)
Implicit object
那么implicit修饰object又是什么鬼呢?
下面是个非常常见的例子:
object Math {
trait NumberLike[T] {
def plus(x: T, y: T): T
def divide(x: T, y: Int): T
def minus(x: T, y: T): T
}
object NumberLike {
implicit object NumberLikeDouble extends NumberLike[Double] {
def plus(x: Double, y: Double): Double = x + y
def divide(x: Double, y: Int): Double = x / y
def minus(x: Double, y: Double): Double = x - y
}
implicit object NumberLikeInt extends NumberLike[Int] {
def plus(x: Int, y: Int): Int = x + y
def divide(x: Int, y: Int): Int = x / y
def minus(x: Int, y: Int): Int = x - y
}
}
}
实际上,看到的隐式object我们可以认为它就是
implicit val NumberLikeDouble: NumberLike[Double] = new NumberLike[Double] {
...
}