2.14.3 使用Saga模式管理跨服务事务
当一切正常时,API组合和CQRS模式可为分布式查询提供适当的解决方案。但是,维护分布式数据的完整性是一个复杂的问题。如果将所有数据存储在单个关系数据库中,并在数据模式中指定适当的约束,则可以依靠数据库引擎来维护数据完整性。这种情况与多个微服务在独立的数据存储(关系或非关系)中维护数据的情况非常不同。数据完整性至关重要,但是它必须由代码维护。Saga模式解决了这个问题,在深入探讨Saga模式之前,让我们先大致了解一下数据完整性。
1.理解ACID
数据完整性的一种常见度量是所有修改数据的事务都具有ACID属性:
·原子性(atomic):事务中的所有操作要么成功,要么失败。
·一致性(consistent):数据状态符合事务前后的所有约束。
·隔离性(isolated):并发事务的行为就像串行化一样。
·持久性(durable):事务成功完成后,该事务对数据库所做的更改将永久地保存在数据库中。
ACID属性并不特定于关系型数据库,但经常用于关系型数据库上下文中,主要是因为关系模式及其形式约束提供了方便的一致性度量。隔离性通常会严重影响性能,在某些倾向于高性能和最终一致性的系统中要求可能会放宽一些。
持久性是很明显的。如果你的数据无法安全地持久存储,那么就没有必要去解决其他所有的问题。持久性也包含不同的级别:
·磁盘持久性:在没有发生磁盘故障时,数据可以在节点重新启动后保留。
·多节点冗余:数据可以在单个节点重新启动或者磁盘故障后保留,但不能是所有节点的临时性故障。
·磁盘冗余:数据可以在某个磁盘故障后保留。
·跨地域的副本:数据可以在某个地域的整个数据中心宕机时保留。
·备份:可以存储大量信息并且便宜得多,但恢复速度较慢,并且经常落后于实时数据。
原子性要求也是理所当然的。没有人喜欢部分更改,这些更改可能会破坏数据完整性,并以某种不可预知的方式破坏系统。
2.理解CAP定理
CAP定理指出,分布式系统不能同时具有以下三个属性:
·一致性(consistency)
·可用性(availability)
·分区容错性(partition resiliency)
在实践中,你可以选择要使用CP系统还是AP系统。CP系统(具有一致性和分区容错性)始终是一致的,并且如果组件之间发生网络分区,则CP系统不会提供查询或进行更改,仅在系统完全连接后才能运行。显然,这意味着你牺牲了可用性。另一方面,AP系统(具有可用性和分区容错性)始终可用,并且可以以脑裂方式(split-brain)运行。当系统发生网络分区时,每个部分可以继续正常运行,但是系统会变得不一致,因为每个部分都不知道其他部分正在发生的事务。
AP系统通常被称为最终一致的系统,因为当恢复连接时,某些协调过程可确保整个系统再次同步。冻结系统是一个有趣的变体,在冻结系统中,当发生网络分区时,它们会优雅降级,并且两个部分都继续处理查询,但拒绝所有对系统进行的修改。注意,由于分区中的某些事务可能仍无法复制到另一部分,因此不能保证两个分区是一致的。通常,这已经足够好了,因为两部分之间的差异很小,而且不会随着时间的推移而增加,因为新的更改都被拒绝了。
3.将Saga模式应用于微服务
关系型数据库可以通过算法(例如两阶段提交)和对所有数据的控制使得分布式系统可以遵从ACID要求。两阶段提交算法分为两个阶段:准备和提交。但是,参与分布式事务的服务必须共享同一数据库,这对于只管理自己数据库的微服务不起作用。
现在我们可以聊聊Saga模式了。Saga模式的基本思想是对所有微服务的操作进行集中管理,如果由于某种原因无法完成整个事务,则对于每个操作都会有一个补偿操作。这实现了ACID的原子性。但是,每个微服务上的更改都是立即可见的,而不仅是在整个分布式事务结束时,这违反了一致性和隔离性。如果将系统设计为AP,也称为最终一致,这就不是问题。但是,它需要你的代码意识到这一点,并且能够处理可能部分不一致或过时的数据。在很多情况下,这是一个可以接受的折中方案。
那Saga如何运作呢?Saga是微服务上的一组操作和相应的补偿操作。当一个操作失败时,它的补偿操作和之前所有操作的补偿操作以相反的顺序调用,以回滚系统的整个状态。
Saga的实现并不简单,因为补偿操作也可能失败。通常,瞬时状态必须是持久性的,并且必须存储大量元数据才能实现可靠的回滚。一个好的实践是让外部流程频繁地运行,并清理无法实时完成其所有补偿操作的故障Saga。
将Saga视为工作流是一个好方法。工作流之所以很酷,是因为它们可以实现长流程,而且支持人为操作,而不仅仅是软件自动化实现。