本篇主要总结第三章:微服务架构中的进程间通信。
微服务的交互方式
在不讨论消息具体实现的情况下,作者从不同维度先对进程间通信的交互方式进行了分类和归纳。
- 客户端与服务的交互方式(维度一)
- 一对一:每个客户端请求由一个服务实例来处理
- 一对多:每个客户端请求由多个服务实例来处理
- 发布/订阅
- 发布/异步响应
- 客户端与服务的交互方式(维度二)
- 同步模式:客户端需要服务端实时响应,等待响应的过程可能导致阻塞
- 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非实时的
微服务架构中定义API
1. API优先设计:作者认为API是软件开发的中心,推荐在微服务中使用API优先设计的开发方式。开发人员先把客户端-服务端之间的API接口定义好之后,再开始各自的编程。
2. API的变更:微服务架构中,API一旦确定就下来,在更新API时必须确保旧新版本API的兼容性。因此,引入API版本控制——语义化版本控制。
语义化版本控制,由3部分组成:MAJOR.MINOR.PATCH。递增版本号规则:
- MAJOR:对API进行不兼容的更改时
- MINOR:对API进行后向兼容的增强时
- PATCH:进行后向兼容的错误修复时
3.后向兼容的更改
在微服务架构中对API尽量只进行后向兼容的更改:
- 添加可选属性
- 向响应添加属性
- 添加新操作
4.非后向兼容的更改
如果必须进行非后向兼容的更改,需要同时支持新旧版本的API,直到所有客户端都升级到新的API。可以通过以下方式实现:
- 在URL中嵌入版本号
- 使用HTTP的内容协商机制,在MIME中包含版本号
消息的格式
消息的格式可分为两大类:文本和二进制
- 文本
- JSON、XML
- 文本消息的弊主要是消息冗长,必须传输属性名造成额外开销
- 二进制
- Protocol Buffers、Avro
基于同步远程过程调用模式的通信
作者主要介绍了REST和gRPC,感觉作者更倾向于gRPC。
- REST
- good
- 简单
- 可以用浏览器或curl命令直接测试HTTP API
- 直接支持请求/响应方式的通信
- HTTP对防火墙友好
- 不需要中间代理,简化了系统架构
- bad
- 只支持请求/响应方式的通信
- 可能导致可用性降低。在REST API调用期间都必须保持在线,没有代理缓冲消息
- 客户端必须知道服务器的URL
- 在单个请求中获取多个资源具有挑战性
- 有时很难将多个更新操作映射到HTTP动词
- good
- gRPC
- 指定二进制消息格式protocol buffers
- good
- 设计具有复杂更新操作的API非常简单
- 高效、紧凑的进程间通信机制,尤其是在交换大量消息时
- 支持在远程过程调用和消息传递过程中使用双向流式消息方式
- 实现了客户端和用各种语言编写的服务端之间的互操作性
- bad
- 与基于REST/JSON的API机制相比,JavaScript客户端使用gRPC的API需要做更多工作
- 旧式防火境况可能不支持HTTP/2
同步通信必须处理局部故障以提高可用性,作者介绍了断路器模式。
断路器:一个远程过程调用的代理,在连续失败次数超过指定阈值后的一段时间内,这个代理会立即拒绝其他调用。
同时分享了Netflix的经验,同步调用一个服务时,客户端需要这样的机制增加健状性:
- 网络超时:等待响应时设定一个超时
- 限制客户端向服务器发出请求的数量:设置向特定服务发起请求的上限,如果请求达到上限,很可能发起更多的请求也无济于事,此时应该限制发起新的请求,立刻失败。
- 断路器模式:监控客户端发出请求的成功和失败数量,如果失败的比例超过一定的阈值,就启动断路器,让后续的调用立刻失效。在经过一定的时候后,客户端应该继续尝试,如果调用成功则解除断路器。
服务发现
在使用同步远程过程调用中,必须使用服务发现机制,以获取需要调用服务的网络位置。作者介绍了服务发现的分类,并推荐使用平台层服务发现模式。
应用层服务发现模式
- 自注册:服务实例向服务注册表注册自己
- 客户端发现:客户端从服务注册表检索可用服务实例的列表,并在它们之间进行负载平衡。
平台层服务发现模式
部署平台为每个服务提供DNS名称、虚拟IP地址和解析为VIP地址的DNS名称。客户端向DNS名称和VIP发出请求,部署平台自动将请求路由到其中一个可用服务实例。
- 第三方注册模式:由第三方负责处理注册,而不是服务本身向服务注册表注册自己
- 服务端发现模式:客户端不再需要查询服务注册表,而是向DNS名称发出请求,对该DNS名称的请求被解析到路由器,路由器查询服务注册表对请求进行负载均衡
基于异步消息模式的通信
作者的观点是,理想情况下,微服务架构下的通信应该尽可能使用异步消息模式,以提高系统的可用性。下面是关于消息的相关概念和定义:
消息传递
发送方将消息写入通道,接收方从通道读取消息。
- 文档
- 命令:一条等同于RPC请求的消息。
- 事件
消息通道
- 点对点
- 发布-订阅
消息代理
- Apache ActiveMQ
- RabbitMQ
- Apache Kafka
异步API规范
服务的异步API规范必须指定消息通道的名称、通过每个通道交换的消息类型及格式。
- 异步操作
- 请求/异步响应式API
- 单向通知API
- 事件发布
基于代理的消息
作者推荐微服务架构下使用基于代理的消息,一是因为发送方不需要知道接收方的网络位置;二是因为消息代理可以缓冲消息直到消息被接方处理。选择消息代理时,需要考虑以下各种因素:
- 支持的编程语言
- 支持的消息标准
- 消息排序:消息保序
- 投递保证
- 持久性:是否持久化到磁盘
- 耐久性:如果接收方重新连接到消息代理,它是否会收到断开连接时发送的消息
- 可扩展性
- 延迟
- 竞争性(并发)接收方:消息代理是否支持并发接收方
消息代理的好处和弊端
Good
-
- 松耦合
- 消息缓存
- 灵活的通信
- 明确的进程间通信
- Bad
- 潜在的性能瓶颈
- 潜在的单点故障
- 额外的操作复杂性
处理并发和消息顺序
- 消息通道分片:通过分片消息通道的方式来扩展接收方,同时又确保消息被处理的顺序。
处理重复消息
- 幂等消息处理器:被相同参数多次调用时,不会产生额外的效果。
- 跟踪消息并丢弃重复消息
事务性消息
事务性消息是指在更新数据库的事务中发布消息:更新数据库和消息发布必须以原子方式进行,否则系统可能昝于不一致状态。
- 使用数据库表作为消息队列
- 事务性发件箱模式:使用数据库表作为临时消息队列。发送消息的服务有一个OUTBOX数据库表。作为创建、更新和删除业务对象的数据库事务的一部分,服务通过将消息插入到OUTBOX表中来发送消息
- 轮询发布数据:通过轮询数据库中的发件箱来发布消息。
- 事务性发件箱模式:使用数据库表作为临时消息队列。发送消息的服务有一个OUTBOX数据库表。作为创建、更新和删除业务对象的数据库事务的一部分,服务通过将消息插入到OUTBOX表中来发送消息
- 使用事务日志拖尾模式发布事件:通过拖尾事务日志发布对数据库所做的更改。
异步消息提高可用性
进程间通信机制与系统可用性之间的关系。同步机制的可用性相对较低,应该尽可能选择异步通信机制来处理服务之间的调用。
- 同步消息会降低可用性
- 最大化一个系统的可用性,就应该设法最小化系统的同步操作量
- 消除同步交互
- 使用异步交互模式
- 复制数据
- 先返回响应,再完成处理
学习总结
整章学习下来,感觉作者比较推崇基于消息代理的异步通信模式。主要原因是提高系统的可用性,降低通信过程中的耦合度。