Soga
Event Sourcing、CQRS和Saga
Event Sourcing 和 CQRS 核心都是异步事件处理。
而 Saga 更进一步,它关注的是多个系统(微服务)之间数据一致性:如何在避免使用分布式事务情况下达到跨系统数据一致性。
Saga设计的三个重点
- 每个系统的领域对象有状态,比如 PENDING、DONE、CANCELLED,跨系统的这些对象的所有状态合起来形成一个状态机,通过状态转换达到“最终一致性”。
- 跨系统之间通过消息来推进状态切换。
- 每个系统在创建 PENDING状态的记录后,立即返回记录 ID,在 UI 一侧通过轮训后端服务或者被动通知获得最终结果。
跨系统之间通过消息来推进状态切换
三种通知方式:
- 系统之间显式调用。【耦合性太紧】
- 整一个 coordinator 服务,它来调用各个系统,维护状态机的转换,很像一个 workflow engine,实际上经常会用一个轻量级的工作流引擎来实现。【此模式也称为process manager】
- 系统之间通过 pub/sub 语义的消息队列通讯,上下游之间松耦合。
第三种方式存在一个细节问题:一个服务操作自己的数据库并发送消息给上游或下游需要是原子的。
而该问题有三种解决方式:
- 找一个支持 JTA 的消息队列,比如 ActiveMQ,然后就可以保证一个微服务的本地数据库操作和往外发消息是原子的。
不依赖 JTA,把消息队列放到微服务的本地数据库里:
- 利用触发器或者微服务本身的应用逻辑,写一个 event table,订阅者消费这张表。这个方案有两个麻烦:订阅者要不断用SELECT来轮训这张表;由于事务的并发,不能保证递增主键的记录其插入时间也一定递增,所以订阅者如果按照递增主键扫描事件,会有可能跳过主键小但时间新的记录,从而丢失事件。
- 利用数据库的事务日志,比如 MySQL 的 binlog,这样总是可以从日志末尾读取下一个事件,确保不会读漏。但复杂度高,且维护困难。
为了让消费者方便消费,一般还需要把各个本地数据库里的“队列”里的消息转发到一个集中的消息队列。
Saga模式的问题
- 最终一致性:这个需要业务逻辑编写人员非常清醒其后果,比如一个跨系统异步事务:A -> B -> C -> D,在 D 成功后反向通知 C/B/A 成功的过程中,如果出问题了,那么 D 的“成功记录”是会留存一段时间,此时如果被系统 E 读取了做了其它业务,然后等 A/B/C/D 协商要撤销 D 的“成功记录” 时,就出问题了。
- 并不单是 Saga 模式的问题,而是所有做法都需要预备的:一个兜底的定期扫描检查任务,找出系统中数据不一致的地方,并修正。理论毕竟是理论,现实情况下即使严格遵循 Saga 模式,也难保不出 bug,导致数据“最终”也一致不了,分布式一致协议本身就是计算机领域的超级大难题。