从单体架构到分布式架构,从巨石架构到微服务架构。系统之间的交互越来越复杂,系统间的数据交互量级也是指数级增长。作为一个系统,我们要保证逻辑的自洽和数据的自洽。
数据自洽有两方面要求:
为了实现这两点,需要实现数据的一致性,为了实现一致性,就需要用到事务。
需要注意一下,本文所设计的数据一致性,不是多数据副本之间保持数据一致性,而是系统之间的业务数据保持一致性。
在早期的系统中,我们可以通过关系型数据库的事务保证数据的一致性。这种事务有四个基本要素:ACID。
这四个要素是关系型数据库的根本。无论系统多么复杂,只要使用同一个关系型数据库,我们就可以借助事务保证数据一致性。基于对关系型数据库的信任,我们可以认为本地事务是可靠的,开发过程中不需要额外的工作。从架构的角度,关系型数据库也是一个单独的系统,那关系型数据库与应用之间也是形成了分布式。所以我们先研究一下这种简单的分布式系统如何实现 ACID。
首先,A(原子性)和 D(持久性)是彼此之间密不可分的两个属性:原子性保证了事务的所有操作,要么全部完成,要么全部失败,不可能停滞在中间某个环节;持久性保证了一旦事务完成,该事务对数据库所作的更改便持久的保存在数据库之中,不会因为任何原因而导致其修改的内容被撤销或丢失。
众所周知,数据必须写入到磁盘后才能保证持久化,仅仅保存在内存中,一旦出现系统崩溃、主机断电等情况,数据就会丢失。所以,关键是“写入磁盘”要实现原子性和持久性,然而这个动作存在中间态:正在写入。所以,现代的关系型数据库通常采用追加日志记录的方式。将修改数据所需的全部信息(包括修改什么数据、数据物理上位于哪个内存页和磁盘块中、从什么值改成什么值,等等),以顺序追加的形式记录到磁盘中。只有在日志记录全部落盘,数据库在日志中看到代表事务成功提交的“提交记录”后,才会根据日志上的信息对真正的数据进行修改。修改完成后,再在日志中加入一条“结束记录”表示事务已完成持久化,这种事务实现方法被称为“提交日志”。
我们能够通过日志保证一个事务的原子性和持久性,那如果出现多个事务访问同一个资源呢?作为程序猿都知道,多个线程/进程访问同一个资源,这个资源就称为临界资源,想要解决临界资源占用冲突的方式很简单,就是加锁。关系型数据库为我们准备了三种锁:
根据这三种锁的不同组合,我们可以实现四种不同的事务隔离级别:
随着系统规模不断扩大,业务量不断增加。单体应用不再满足需求,我们会拆分系统,然后拆分数据库。此时,同一个请求中,就会出现同时访问多个数据库的情况。为了解决这种情况的数据一致性问题,X/Open 组织在 1991 年(那个时候我还小)提出了一套 X/Open XA 的处理事务的架构。XA 的核心内容是定义了全局的事务管理器(Transaction Manager,用于协调全局事务)和局部的资源管理器(Resource Manager,用于驱动本地事务)之间的通信接口,在一个事务管理器和多个资源管理器(Resource Manager)之间形成通信桥梁,通过协调多个数据源的一致动作,实现全局事务的统一提交或者统一回滚。与 XA 架构配套的是两阶段提交协议(2PC,Two Phase Commitment Protocol)。在这个协议中,最关键的点就是,多个数据库的活动,均由一个事务协调器的组件来控制。具体的分为 5 个步骤:
两阶段提交协议实现简单,但存在几个明显缺陷:
能够发现问题,就能够想到办法解决。我们高中老师说了,只要意识不滑坡,办法总比困难多。所以又发展出了三阶段提交协议(3PC,Three Phase Commitment Protocol),能够缓解单点问题和准备阶段的性能问题。这个协议把 2PC 中的准备阶段拆分为 CanCommit 和 PreCommit,把提交阶段改名为 DoCommit。CanCommit 是询问阶段,让每个资源管理器根据自身情况判断该事务是否有可能完成。
3PC 本质是通过一次问询,如果大家都说自己可以,那成事的可能性很大,减少了准备阶段直接锁定资源的重操作。由于事务失败回滚概率变小的原因,在三段式提交中,如果在 PreCommit 阶段之后发生了事务管理器宕机,即资源管理器没有能等到 DoCommit 的消息的话,默认的操作策略将是提交事务而不是回滚事务或者持续等待,这就相当于避免了事务管理器单点问题的风险。
说到分布式事务,不得不提 CAP 理论:任何分布式系统只可同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)中的两点,没法三者兼顾。
CAP 理论
既然 CAP 不能兼顾,那我们来看看缺少其中一环会出现什么情况:
这里需要再说明一下,我们选择 AP 放弃 C 不是放弃数据一致,而是暂时放弃强一致性(Strong Consistency),而是选择弱一致性,即最终一致性(Eventual Consistency):系统中的所有数据副本经过一段时间后,最终能够达到一致的状态。这里所说的一段时间,也要是用户可接受范围内的一段时间。
最终一致性也有一个理论支撑:BASE 理论(不得不说,理论界的缩写真牛啊,ACID 是酸,CAP 是帽子,BASE 是碱),内容主要包括:
在工程实践中,最终一致性分为 5 种,这 5 种方式会结合使用,共同实现最终一致性:
有了理论之后,我们来说一下实现最终一致性的几种模式。
可靠事件模式属于事件驱动架构:当某个事件发生时,例如更新一个业务实体,服务会向消息代理发布一个事件。消息代理会向订阅事件的服务推送事件,当订阅这些事件的服务接收此事件时,就可以完成自己的业务,也可能会引发更多的事件发布。
我们通过一个例子来解释一下这种模式,用户下单成功后,订单系统需要通知库存系统减库存。
订单系统根据用户操作完成下单操作。此时会使用同一个本地事务保存订单信息和写入事件。
另外一个消息服务会轮询事件表,将状态是“进行中”的事件以消息形式发送到消息服务中。如果发送失败,因为是轮询任务,会在下一次轮询的时候再次发送。(此处有一些优化点,本例为了简化模型,不展开)
消息服务向订阅下单消息的库存服务发送下单成功消息,库存服务开始处理。此时会有这么集中情况:
库存服务扣减库存成功,消息服务接收到处理成功响应。消息服务将响应结果返回给订单服务,订单服务中事件接收器将事件修改为“已完成”。
库存服务扣减库存失败,消息服务接收到处理失败响应。此时消息服务会再次向库存服务发送消息,直到得到成功响应。如果失败次数达到阈值,可以告警通知人工介入。
消息服务给订单服务返回结果时,发生失败,订单服务没有接收到成功响应。这个时候,事件轮询逻辑会再次将事件发送给消息服务。这样,库存服务会重复收到扣减库存的消息,所以要求库存服务做好幂等。库存服务发现消息已经处理过,直接返回成功。
这种靠着持续重试来保证可靠性的解决方案,叫做“最大努力交付”(Best-Effort Delivery),也是“可靠”两个字的来源。
可靠事件模式还有一种更普通的形式,被称为“最大努力一次提交”(Best-Effort 1PC),指的就是将最有可能出错或最核心的业务以本地事务的方式完成后,采用不断重试的方式(不限于消息服务)来促使同一个分布式事务中的其他关联业务全部完成。找到最可能出错的方式是提前做好出错概率的先验评估,才能够知道哪块最容易出错。找到最核心的业务的方式是找到那种只要成功,其他业务必须成功的那块业务。
这里我们再补充两个概念:
TCC(Try-Confirm-Cancel)是一种业务侵入式较强的事务方案,要求业务处理过程必须拆分为“预留业务资源”和“确认/释放消费资源”两个子过程,由统一的服务协调调度不同业务系统的子过程。分为以下三个阶段:
订单系统创建事务,生成事务 ID(用于作为识别请求幂等的标识),通过活动管理器记录活动日志。
进入 Try 阶段
调用账户系统,检查账户余额是否充足,如果充足,冻结需要的金额,此时账户余额是临界资源,需要通过排它锁或乐观锁保证冻结操作的安全性。
调用库存系统,检查商品库存是否充足,如果充足,锁定需要的库存,锁库操作也需要加锁保证安全
如果所有业务返回成功,记录活动日志为 Confirm,进入 Confirm 阶段:
调用账户系统,扣减冻结的金额
调用库存系统,扣减锁定的库存
第 3 步操作中如果全部完成,事务宣告结束。如果第 3 步中任何一方出现异常,都会根据活动日志中的记录,重复执行 Confirm 操作,即进行最大努力交付。所以各业务系统的 Confirm 操作需要实现幂等性。
如果第 2 步有任何一方失败(包括业务异常和技术异常),将活动日志记录为 Cancel,进入 Cancel 阶段:
调用账户系统,释放冻结的金额
调用库存系统,释放锁定的库存
第 5 步操作中如果全部完成,事务宣告失败。如果第 5 步中任何一方出现异常(包括业务异常和技术异常),都会根据活动日志中的记录,重复执行 Cacel 操作,即最大努力交付。所以各业务系统的 Cancel 操作也需要实现幂等性。
是不是感觉 TCC 与 2PC 的很像,两者的区别在于,TCC 位于业务代码层面,属于白盒,2PC 位于基础设施层面,属于黑盒。所以 TCC 有更高的灵活性,可以根据需要,调整资源锁定的粒度。
TCC 在业务执行过程中可以预留资源,解决了可靠事件模式的资源隔离问题。但是,TCC 还有两个明显缺点:
鉴于上面的两个缺点,我们看看 SAGA 是否可以弥补。
SAGA 在英文中是“长篇故事、长篇记叙、一长串事件”的意思。SAGA 模式的提出远早于分布式事务概念的提出(再次对前辈大佬佩服的五体投地),它源于 1987 年普林斯顿大学的 Hector Garcia-Molina 和 Kenneth Salem 在 ACM 发表的一篇论文《SAGAS》。文中提出了一种提升“长时间事务”(Long Lived Transaction)运作效率的方法,大致思路是把一个大事务分解为可以交错运行的一系列子事务集合,后来发展成将一个分布式环境中的大事务分解为一系列本地事务的设计模式。在有的文章中,将这种模式称为业务补偿模式,SAGA 是对事务形式的描述,业务补偿是对事务行为的描述,其本质是一样的。
SAGA 模式有两种实现:
根据这两种实现,SAGA 可以分为两部分:
子事务与补偿动作需要满足一些条件:
本文主要总结了本地事务、全局事务、最终一致性等方式实现数据自洽。重点介绍了实现最终一致性的集中模式:可靠事件模式、TCC 模式、SAGA 模式等。数据的一致性一直是个难题,随着微服务化之后,数据一致性更加困难,有困难不怕,只要不放弃,总会解决的。
作者:看山
链接:
来源:稀土掘金
相关资源:
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- 517ttc.cn 版权所有 赣ICP备2024042791号-8
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务