充当服务之间的中介.
3.3.1 什么是消息传递
关于消息
组成:
- 消息头: key-value 形式
- 发件人
- 唯一消息 ID
- 回复消息的通道
- 消息体
- 格式: 文本或二进制
- 类型
- 文档
- 命令
- 事件
关于消息通道
消息通道的类型:
- 点对点通道, 命令式消息常用
- 发布-订阅通道, 事件式消息常用
3.3.2 使用消息机制实现交互方式
消息机制可以实现所有交互方式.
实现请求/响应和异步请求/响应
区别:
- 对于请求/响应, 客户端期望服务立即响应
- 而对于异步请求/响应, 则没有这样的期望
异步实现:
- 请求通道是服务创建的
- 回复通道是客户端创建的
实现单向通知
与图3-8类似, 只是服务端不回复消息.
- 点对点
实现发布/订阅
与图3-8类似.
实现发布/异步响应
组合:
- 一对多关系 (发布-订阅)
- 请求/响应
3.3.3 为基于消息机制的服务 API 创建 API 规范
服务的异步 API 规范需指定:
- 消息通道的名称
- 通过每个通道交换的消息类型及其格式
记录异步操作
可以使用以下两种不同交互方式调用服务的操作:
记录事件发布
服务可以使用发布/订阅的方式对外发布事件.
API 规范:
- 事件通道
- 服务发布到通道的事件式消息的类型和格式
3.3.4 使用消息代理
这里的消息代理指消息中间件.
有无代理的区别:
无代理消息
好处:
弊端:
基于代理的消息
好处:
- 发送方不需要知道接收方的网络位置
- 消息代理缓冲消息, 直到接收方能够处理它们
开源消息代理:
- Apache ActiveMQ
- RabbitMQ
- Apache Kafka
- AWS Kinesis
选择消息代理需要考虑的因素:
使用消息代理实现消息通道
不同实现:
基于代理的消息的好处和弊端
好处:
弊端:
3.3.5 处理并发和消息顺序
确保每个消息只被处理一次, 并且按照发送的顺序来处理.
业务上产生的消息是有逻辑顺序的, 在交给接收方处理时也要按照顺序处理.
使用分片 (分区) 保证顺序:
本质是利用分片, 将相同 shard-key 分配给同一实例, 这样业务上顺序的消息会被同一实例顺序处理.
3.3.6 处理重复消息
保证有且仅有一次的消息传递通常成本很高, 大多数消息代理承诺至少成功传递一次消息.
处理重复消息的方法:
- 编写幂等消息处理程序
- 跟踪消息并丢弃重复项
编写幂等消息处理器
满足幂等的消息处理程序可以被放心地执行多次, 只要消息代理在重新传递消息时保持相同的消息顺序
跟踪消息并丢弃重复消息
使用 message id 跟踪已处理消息:
- 如果使用数据库, 那么保存 message id 和业务数据可以在一个事务中
- 如果使用 NoSQL, 那么保存 message id 的表应该放在应用程序中
3.3.7 事务性消息
操作数据库和发送消息可能要组成事务来执行.
分布式事务.
使用数据库表作为消息队列
通过轮询模式发布事件
让 MessageRelay 轮询 OUTBOX 表.
使用事务日志拖尾 (tailing) 模式发布事件
让 MessageRelay 拖尾数据库的事务日志文件 (提交日志).
实际的应用案例和实现:
3.3.8 消息相关的类库和框架
直接使用消息代理的客户端库的问题:
作者在本书中使用了他开发的框架 Eventuate Tram.
Eventuate Tram 支持的交互方式:
- 发送, 接收消息的 API
- 异步请求/响应
- 领域事件发布
Eventuate Tram 实现的两个重要机制:
- 事务性消息机制: 将消息作为数据库事务的一部分发布
- 重复消息检测机制: 支持消息接收方检测并丢弃重复消息
基础消息 API
- MessageProducer: 发布消息
- MessageConsumer: 订阅消息
用于异步请求/响应和领域事件发布.
领域事件发布 API
- DomainEventPublisher: 发布事件
- DomainEventDispatcher: 消费事件
基于命令/回复的消息
同步的?
- CommandProducer: 发送命令
- CommandDispatcher: 接收命令