本篇主要总结第四章:解决分布式事务解决方案——Saga
分布式事务
分布式事务,是伴随分布式系统的出现而产生的。在分布式系统中,天然存在跨多个服务的事务,如何确保分布式事务的ACID特性成为了分布式系统的重要研究课题。
Saga
一种解决分布式事务的模式。作者比较推荐,整个第4章都是介绍Saga的原理、设计和实现。Saga的特点是通过补偿事务来回滚。
Saga按照协调逻辑分为:
- 协同式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式来进行沟通。
- 编排式:把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方服务完成具体操作。
1. 协同式Saga
- Saga参与方将更新其本地数据库和发布事件作为数据库事务的一部分(事务性消息)。数据库更新和事件发布必须是在同一个原子操作内完成。
- Saga参与方必须能够将接收到的每个事件映射到自己的数据上
1.1协同式Saga的优劣
- Good
- 简单
- 松耦合
- Bad
- 更难理解
- 服务之间循环依赖
- 紧耦合的风险
2.编排式Saga
- Saga编排器使用异步请求/响应调用Saga参与方
- Saga状态机
- 事务性消息
2.1编排式Saga的优劣
- Good
- 没有循环依赖
- 较少的耦合:每个参与方只需要实现API,不需要关注Saga参与方发布的事件
- 改善关注点隔离,简化业务逻辑
- Bad
- 在编排器中存在集中过多业务逻辑的风险
3.Saga的隔离性
隔离性:确保同时执行多个事务的结果 与 顺序执行这些事务的结果相同
缺乏隔离导致的问题
- 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改。
- 脏读:一个事务或一个Saga读取了尚未完成的Saga所做的更新。该更新可能被回滚从而失效,如果事务拿着失效的数据去处理就会发生错误。
- 不可重复读:在事务A两次读取同一记录的过程中,事务B对该记录进行了修改(更新已提交),从而事务A第二次读到了不一样的记录。
- 幻读:事务A在两次查询的过程中,事务B对该表进行了插入、删除操作(新增或删除已提交),从而事务A第二次查询的结果集发生了变化(新增数据)
4.Saga下实现隔离的策略
- 语义锁:应用程序级的锁
- Good
- 实现了ACID
- 消除了客户端重试
- Bad
- 应用程序必须管理锁,防止死锁
- Good
- 交换式更新:把更新操作设计成可以按任何顺序执行
- 悲观视图:重新排序Saga的步骤,以最大限度地降低业务风险
- 重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变(乐观锁)
- 版本文件:将更新记录下来,以便可以对它们重新排序
- 业务风险评级:使用每个请求的业务风险来动态选择并发机制
5.Saga中三种类型的事务
- 可补偿性事务:可以使用补偿事务回滚的事务
- 关键性事务:Saga执行成败的关键点。如果关键性事务成功,则Saga将一直运行到完成。
- 可重复性事务:在关键性事务之后的事务,保证成功。
学习总结
这章中作者提出了微服务或者SOA这类分布式系统都会遇到的一个问题:分布式事务如何确保ACID。这里的分布式事务是相对于单体应用中本地事务而言的,本地事务依赖数据库提供的ACID,分布式事务则需要另外一套解决方案。
这里必须再次提及著名的ACID原则以及事务的隔离级别:
- Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务,不可分割,不可约简。
- Consistency(一致性):事务开始之后和事务结束之后,数据库的完整性没有被破坏。写入的数据必须完全符合所有以预设约束、触发器、级联回滚等。数据库事务的一致性就规定了事务提交前后,永远只可能存在事务提交前的状态和事务提交后的状态,从一个一致性的状态到另一个一致性状态,而不可能出现中间的过程态。
- Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括 未提交读、提交读、可重复读和串行化。
- Durability(持久性):事务处理结束后,对数据的修改就是永久的,即使系统故障也不会丢失。
需要说明一下,事务的隔离级别,从低到高分为:
- Read uncommitted(读未提交):在该级别下,一个事务对一行数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据读。 因此本级别下,不会出现更新丢失,但会出现脏读、不可重复读。
- Read committed(读提交):在该级别下,未提交的写事务不允许其他事务访问该行,因此不会出现脏读;但是读取数据的事务允许其他事务访问该行数据,因此会出现不可重复读的情况。
- Repeatable read(重复读):在该级别下,读事务禁止写事务,但允许读事务,因此不会出现同一事务两次读到不同的数据的情况(不可重复读),且写事务禁止其他一切事务。
- Serializable (序列化): 该级别要求所有事务都必须串行执行,因此能避免一切因并发引起的问题,但效率很低。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted | 可能 | 可能 | 可能 |
Read Committed | 不可能 | 可能 | 可能 |
Repeatable Read | 不可能 | 不可能 | 可能 |
Serializable | 不可能 | 不可能 | 不可能 |
隔离级别和数据库的性能是呈反比的,隔离级别越低,数据库性能越高,而隔离级别越高,数据库性能越差。
回到分布式事务,自从开始使用分布式系统就有牛人不断提出解决分布式事务的理论和方案。
CAP理论(三选二):
一个分布式系统最多只能同时满足一致性(Consisitency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。前两项都好理解,分区容错性是指分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。这个理论让我想起了经济学上的不可能三角(资本自由流动、固定汇率和货币政策独立性)也是个三选二的理论。
一般的选择是:可用性(Availability)和分区容错性(Partition tolerance)。做这样的选择原因很简单:如果分布式系统不能分区容错还要分布式干嘛。
BASE理论
前面的CAP理论非要3选2,但是放弃一致性显然也是不可能的,但是可以从强一致性降低为最终一致性,理论依据就是BASE。BASE理论是Basically Available(基本可用),Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写。其核心思想是:
既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
2PC、3PC、TCC、Saga、Paxos和Raft
BASE理论需要在一致性和可用性方面做出权衡,因此涌现了很多关于一致性的算法和协议,有兴趣可以深入了解一下。
Saga模式优势是
- 一阶段提交本地数据库事务,无锁,高性能;
- 参与者可以采用事务驱动异步执行,高吞吐;
- 补偿服务即正向服务的“反向”,易于理解,易于实现;
Saga的缺点也非常明显:
- 只有ACD,不能保证隔离性I。
参考文献:
CAP 定理的含义 - 阮一峰的网络日志 分布式理论(二) - BASE理论