集成
理想的集成技术
- 避免破坏性修改
- 一个服务的修改会导致服务的消费方也发生变化
- 保证API的技术无关性
- 也就说,API不管使用什么技术,应该都能实现,保证通信方式的技术无关性是非常重要的,这样各个服务才有可能使用不同的技术实现
- 使服务易于消费方使用
- 隐藏内部实现细节
- 如果消费方与服务的实现细节绑定在一起,会增加两者间的耦合
通信
交互方式
- 一对一与一对多
- 同步与异步
模式 | 一对一 | 一对多 |
---|---|---|
同步模式 | 请求/响应 | 无 |
异步模式 | 异步请求/响应 单向通知 | 发布/订阅 发布/异步响应 |
同步还是异步
同步通信,发起一个远程调用后,会阻塞自己并等待整个操作的完成
异步通信,则不需要等待操作结束就可以访问
使用哪种方式,要取决于哪种风格的通信解决的问题
API定义
如何定义API取决于进程间通信机制。
随着应用演进,API也会随着演进。
- 次要且向后兼容的演进:增加可选属性、添加新操作等
- 主要且不向后兼容的演进:此时可以引入版本号
消息格式
- 基于文本消息格式:xml、json。好处在于可读性高、自描述。缺点在于消息过度冗长。
- 二进制消息格式:protocol buffers、avro。编译器根据定义的IDL生成序列化与反序列代码。好处性能高。
跨服务业务流程
有两种方式:编排与协同
编排是有一个控制中心,指导其他服务应该做些什么,具体怎么做,则交给具体服务
事件发生:
控制中心调用服务A
控制中心调用服务B
使用这种方式的缺点是会让控制中心承担太多的职责,并会导致少量的“上帝服务(上帝视角)”
若使用协同,则是可以客户触发一个事件,监听到这个事件的具体服务去做一些事情
事件发生:
服务A接收到事件,做一些事
服务B接收到事件,做一些事
这个方式的优点是能显著地消除耦合,但是缺点是无法看到清晰的业务流程,所以这种方式需要一定的监控手段来保证业务的正确性
断路器模式处理局部故障
服务保护自己的方式:
- 网络超时:对某个服务的超时不能无限
- 限制客户端的请求数量
- 断路器模式
服务发现
- 应用层:服务直接与服务注册表交互
好处在于可以处理多平台部署问题,弊端则是需要为每种编程语言提供一个SDK。
- 平台层:使用基础设施来实现服务发现
同步的编排方式
远程过程调用(RPC)
在分布式计算,远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)
一些问题
- 技术耦合
- 如果使用JAVA RMI,就会将服务端与客户端都绑定在JVM上
- 远程调用的开销
- 网络是不可靠的
- 脆弱
- 一端的修改很容易影响到另外一端
REST
REST是RPC的一种替代方案
REST成熟度模型
- 0:每个请求都指明了需要执行的操作和必要的参数
- 1:引入了资源的概念,对资源进行操作
- 2:使用HTTP动词来执行操作
- 3:基于HAEOAS,超文本驱动,客户端无需硬编码REST链接。让客户端自行遍历与发现API,可以很好地隐藏低层细节,使得客户端与服务端之间实现了松耦合
REST与HTTP
REST本身没有定义应该使用哪种协议实现,但是使用HTTP协议,会简单的很多
载体形式
- JSON
- XML
过多的约定
数据持久化继承并非是一件需要过早操心的事,最好是先设计外部接口,这样可以确保服务的接口是由消费者的需求驱动出来的
缺点
基于HTTP的REST有一些缺点:
- 时延不低
- 数据包不够精简
- 需要手动编写客户端代码
- 有些框架HTTP动词支持不好
gRPC
service UserService {
rpc createUser(CreateUserRequest) returns(CreateUserReply) {}
}
...
- 高效紧凑
- 支持双向流式
异步的协作方式
同步消息会降低可用性,为消除同步交互,可:
- 使用消息代理来进行异步交互
- 复制数据来避免与其他服务同步交互
异步协作的两种架构:
- 消息代理:需要一些中间件来实现异步协作,尽量让中间件保持简单
- 耦合低
- 更为灵活
- 额外的复杂性
- 无代理架构:直接向服务发送消息
- 性能较高
- 复杂性较低
- 耦合过紧
可实现的交互方式:
- 请求/响应模型
- 单向通知
- 发布订阅
- 发布/响应
API规范
异步操作:
- 请求/异步响应API
- 单向通知API
记录事件发布API
技术选择
- MQ
- 基于HTTP的发布订阅模式
异步架构复杂性
采用异步架构,要考虑的事情就更多了
- 并发与消息顺序
- 在kafka中,使用了分片来解决顺序
- 重复消息
- 应用程序需要自己进行幂等处理
- 事务性消息
- 消息队列表与分布式事务
服务即状态机
服务拥有在限界上下文中的所有逻辑,这样可以在唯一一个地方处理逻辑
响应式扩展
把多个调用的结果组装起来,并在此上做操作(类似于stream)
微服务中代码复用的危险
不同的服务复用同一块代码,一个服务修改的代码很可能影响另一个服务
按引用访问
在进行事件通知时,传递的数据应该是指向资源的一个引用,这样当其他服务处理这个事件时,就可以根据这个引用得到最新的数据,而避免数据不一致的情况
版本管理
- 尽可能推迟修改
宽进严出原则:对自己发送的东西要严格,对接收的东西可以宽容一点
及早发现破坏性修改
使用语义化的版本管理
- 通过版本号来告知消费方功能增加或是否向后兼容
多版本接口共存
多版本服务共存
用户界面
- 数字化
- 未来的需求很难预测,提供细粒度的API
- 不同场景的约束
- API的组合
- 使用网关来缓解客户端与服务之间的过多交互
- 服务直接提供UI片段
为前端服务的后端
再在服务之上封装一个API提供粗粒度的接口,这些接口通过调用下层的服务来提供服务
但是这层API很容易发展为一个怪兽,并且可能会有业务逻辑混入其中
集成第三方软件
一些风险:
- 缺乏控制