title: “ServiceComb中的数据最终一致性方案 - part 3” lang: cn ref: distributed_saga_3 permalink: /cn/docs/distributed_saga_3/ excerpt: “Saga和其他数据一致性解决方案相比有什么不同之处?” last_modified_at: 2017-09-18T15:22:00+08:00 author: Sean Yin tags: [事务一致性] redirect_from:
在我的前一篇[文章]({{ site.baseurl }}{% link _posts/cn/2017-09-16-saga-design.md %}),我谈到了ServiceComb下的Saga是怎么设计的。 然而,业界还有其他数据一致性解决方案,如两阶段提交(2PC)和Try-Confirm / Cancel(TCC)。那saga相比之下有什么特别?
两阶段提交协议是一种分布式算法,用于协调参与分布式原子事务的所有进程,以保证他们均完成提交或中止(回滚)事务。1
2PC包含两个阶段:
![voting phase]({{ site.url }}{{ site.baseurl }}/assets/images/saga.2pc.phase1.png){: .align-center}
![decision phase]({{ site.url }}{{ site.baseurl }}/assets/images/saga.2pc.phase2.png){: .align-center}
在投票阶段结束之后与决策阶段结束之前,服务处于不确定状态,因为他们不确定交易是否继续进行。当服务处于不确定状态并与协调器失去连接时, 它只能选择等待协调器的恢复,或者咨询其他在确定状态下的服务来得知协调器的决定。在最坏的情况下, n个处于不确定状态的服务向其他n-1个服务咨询将产生**O(n2)**个消息。
另外,2PC是一个阻塞协议。服务在投票后需要等待协调器的决定,此时服务会阻塞并锁定资源。由于其阻塞机制和最差时间复杂度高, 2PC不能适应随着事务涉及的服务数量增加而扩展的需要。
TCC也是补偿型事务模式,支持两阶段的商业模型。
![try phase]({{ site.url }}{{ site.baseurl }}/assets/images/saga.tcc.try.png){: .align-center}
![confirm phase]({{ site.url }}{{ site.baseurl }}/assets/images/saga.tcc.confirm.png){: .align-center}
与saga相比,TCC的优势在于,尝试阶段将服务转为待处理状态而不是最终状态,这使得设计相应的取消操作轻而易举。
例如,电邮服务的尝试请求可将邮件标记为准备发送,并且仅在确认后发送邮件,其相应的取消请求只需将邮件标记为已废弃。但如果使用saga, 事务将发送电子邮件,及其相应的补偿事务可能需要发送另一封电子邮件作出解释。
TCC的缺点是其两阶段协议需要设计额外的服务待处理状态,以及额外的接口来处理尝试请求。另外,TCC处理事务请求所花费的时间可能是saga的两倍, 因为TCC需要与每个服务进行两次通信,并且其确认阶段只能在收到所有服务对尝试请求的响应后开始。
有关TCC的更多细节可参考Transactions for the REST of Us.
和TCC一样,在事件驱动的架构中,长活事务涉及的每个服务都需要支持额外的待处理状态。接收到事务请求的服务会在其数据库中插入一条新的记录, 将该记录状态设为待处理并发送一个新的事件给事务序列中的下一个服务。
因为在插入记录后服务可能崩溃,我们无法确定是否新事件已发送,所以每个服务还需要额外的事件表来跟踪当前长活事务处于哪一步。
![event driven architecture - request]({{ site.url }}{{ site.baseurl }}/assets/images/saga.event.driven.request.png){: .align-center}
一旦长活事务中的最后一个服务完成其子事务,它将通知它在事务中的前一个服务。接收到完成事件的服务将其在数据库中的记录状态设为完成。
![event driven architecture - response]({{ site.url }}{{ site.baseurl }}/assets/images/saga.event.driven.response.png){: .align-center}
如果仔细比较,事件驱动的架构就像非集中式的基于事件的TCC实现。如果去掉待处理状态而直接把服务记录设为最终状态,这个架构就像非集中式的基于事件的saga实现。 去中心化能达到服务自治,但也造成了服务之间更紧密的的耦合。假设新的业务需求在服务B和C之间的增加了新的流程D。在事件驱动架构下,服务B和C必须改动代码以适应新的流程D。
![event driven architecture - coupling]({{ site.url }}{{ site.baseurl }}/assets/images/saga.event.coupling.png){: .align-center}
Saga则正好相反,所有这些耦合都在saga系统中,当在长活事务中添加新流程时,现有服务不需要任何改动。
更多细节可参考Event-Driven Data Management for Microservices.
这个Saga系列的文章讨论的都是集中式的saga设计。但saga也可用非集中式的方案来实现。那么非集中式的版本有什么不同?
非集中式saga没有专职的协调器。启动下一个服务调用的服务就是当前的协调器。例如,
![decentralized saga]({{ site.url }}{{ site.baseurl }}/assets/images/saga.decentralized.png){: .align-center}
与集中式相比,非集中式的实现具有服务自治的优势。但每个服务都需要包含数据一致性协议,并提供其所需的额外持久化设施。
我们更倾向于自治的业务服务,但服务还关联很多应用的复杂性,如数据一致性,服务监控和消息传递, 将这些棘手问题集中处理,能将业务服务从应用的复杂性中释放,专注于处理复杂的业务,因此我们采用了集中式的saga设计。
另外,随着长活事务中涉及的服务数量增长,服务之间的关系变得越来越难理解,很快便会呈现下图的死星形状。
![death star architecture]({{ site.url }}{{ site.baseurl }}/assets/images/saga.death.star.png){: .align-center} 图片来源: http://www.slideshare.net/BruceWong3/the-case-for-chaos (s12) {: .figure-caption}
同时,在长活事务中定位问题也变得更加复杂,因为服务日志遍布群集节点。
本文将saga与其他数据一致性解决方案进行了比较。Saga比两阶段提交更易扩展。在事务可补偿的情况下, 相比TCC,saga对业务逻辑几乎没有改动的需要,而且性能更高。集中式的saga设计解耦了服务与数据一致性逻辑及其持久化设施, 并使排查事务中的问题更容易。