数据库分布式事务一致性优化方案转载
导读
本篇是一次关于数据库分布式事务的内容干活,从分布式事务的介绍和各种问题的解决方案,如笔者所说分布式事务的解决方案没有完美的,希望通过本篇文章能给各位一点点启发。
正文
1、什么是分布式事务
1.1 事务
数据库事务(简称:事务,Transaction)是指数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。众所周知,事务拥有 ACID 四个特性,即:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )、持久性( Durability )。
1.2 分布式事务
区别于基于单个服务单一数据库资源访问的本地事务,分布式事务指事务的参与者、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用。分布式事务需要保证这些操作要么全部成功,要么全部失败,保证不同数据库的数据一致性。
本地事务主要限制在单个会话内,不涉及多个数据库资源。但是在基于 SOA ( Service-Oriented Architecture,面向服务架构)的分布式应用环境下,越来越多的应用要求对多个数据库资源、多个服务以及多个服务对应的数据库资源的访问都能纳入到同一个事务当中,分布式事务应运而生。
1.3 数据一致性的基础理论
弱一致性 Weak:系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会承诺具体多久之后可以读到。比如:某些 cache 系统,百度搜索引擎。
最终一致性 Eventually:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。比如:DNS、电子邮件、Amazon S3、Google 搜索引擎这样的系统。
强一致性 Strong:新的数据一旦写入,任何多个后续进程或者线程的访问都会返回最新的更新过的值。比如:文件系统,RDBMS,Azure Table 都是强一致性的。
1.4 CAP 定理
CAP 理论由加州大学伯克利分校的计算机教授 Eric Brewer 在 2000 年提出,其核心思想是任何基于网络的数据共享系统最多只能满足数据一致性( Consistency )、可用性 ( Availability )和网络分区容忍( Partition Tolerance )三个特性中的两个。
C (一致性):对于分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回,合理的响应指的是系统应该明确返回结果并且结果是正确的。
P (分区容错性):当出现网络分区后,系统能够继续工作。比如一个集群中有多台机器,某台机器网络出现了问题,但是这个集群应当仍然可以正常工作。
对于 CA 来说,放弃了 P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是 A 又不允许,在分布式系统中,无法保证网络可靠,分区是一个必然现象,所以分布式系统理论上不可能选择 CA 架构。
对于 CP 来说,放弃可用性,追求一致性和分区容错性,我们的 ZooKeeper 其实就是追求的强一致。
对于 AP 来说,放弃一致性(这里指强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的 BASE 也是根据 AP 来扩展。
因此人们会按照 CAP 理论在对热门的分布式系统进行判定,譬如认为 HBase、MongoDB 等是一个 CP 系统,Cassandra、Dynamo 等是 AP 系统,其实并不严谨,因为 CAP 理论是对分布式系统中一个数据无法同时达到可用性和一致性的断言,而一个系统中往往存在很多类型的数据,部分数据(比如金额类)是需要强一致性的,而另外一部分数据(比如访问量等)并不要求强一致性,所以拿 CAP 并不是来划分系统的,而是指引设计者在设计分布式系统时需要区分各种数据的特点,并仔细考虑在小概率的网络分区发生时究竟为该数据选择可用性还是一致性。
1.5 BASE 理论
核心思想:
基本可用( Basically Available ):指分布式系统在出现故障时,允许损失部分的可用性来保证核心功能可用。
软状态( Soft State ):指允许分布式系统存在中间状态(也叫软状态),该中间状态不会影响到系统的整体可用性。
最终一致性( Eventual Consistency ):经过一段时间后,所有的分布式节点数据达到一致状态。
BASE 理论通过牺牲强一致性来获取最终一致性,当出现故障时允许部分不可用但要保证核心功能可用,允许数据在一段时间内不一致,但最终达到一致状态,满足 BASE 理论的事务称之为柔性事务。
二、分布式事务一致性解决方案
2.1 两阶段提交( 2PC )
2PC( Two Phase Commitment Protocol )是分布式事务中最经典的事务类型之一。
两个角色
一个事务协调者( coordinator ):负责协调多个参与者进行事务投票及提交或回滚;多个事务参与者( participants ):即本地事务执行者。
两个阶段步骤
阶段一:提交事务请求(投票阶段)
1. 事务询问:协调者向所有的参与者发送事务内容,询问是否可以执行事务提交,等待回应;
2. 执行事务:各节点参与者执行本地事务,并记录 Undo 和 Redo 日志;
3. 参与者反馈事务:如果成功执行事务,向协调者反馈 Yes,否则 No。
阶段二:执行事务提交
如果阶段一反馈都是 Yes,则:
协调者发起事务提交的请求;
参与者提交事务( Commit );
反馈事务提交结果;
完成事务。
如果阶段一有反馈 No,则:
2、参与者事务回滚( Rollback );
3、返回回滚事务结果;
4、中断事务。
如果任一资源管理器在第一阶段返回准备失败,那么事务管理器会要求所有资源管理器在第二阶段执行回滚操作。通过事务管理器的两阶段协调,最终所有资源管理器要么全部提交,要么全部回滚,最终状态都是一致的。
3PC
两段提交最大的问题就是:第二阶段时参与者没有收到协调者的 Commit / Rollback 的指令,参与者就会一直处于等待指令状态,事务就会阻塞住。因此协调者的可用性是个关键。
进而引出了升级版的三段提交 3PC。3PC 把 2PC 的第一个阶段变成了两段,其核心理念是:在询问的时候并不锁定资源,除非所有人都同意了才开始锁资源。但三阶段提交也存在一些缺陷,即:如果参与者收到了 preCommit 消息后出现了网络分区,那么参与者等待超时后都会进行事务的提交,必然会出现事务不一致的问题。
优缺点
优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。
缺点:由于是强一致性,资源需要在事务内部等待,性能影响较大,吞吐率不高;
不适合高并发与高性能的业务场景,因此并不适合在微服务架构体系下使用
2.2 补偿事务 TCC( Try-Confirm-Cancel )
TCC 将事务提交分为 Try - Confirm - Cancel 三个操作。是改进版的二阶段提交,Try 为第一阶段,Confirm - Cancel 为第二阶段。
1、Try:业务校验,预留必需业务资源。
2、Confirm:真正执行业务,不再做业务校验;使用 Try 阶段预留的业务资源;Try 成功,Confirm 必定成功,需满足幂等性。
3、Cancel:释放 Try 阶段预留的业务资源,需满足幂等性。
其核心在于将业务分为两个操作步骤完成,不依赖 RM 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
- 当所有 try 方法均执行成功时,对全局事务进行提交,即由事务管理器调用每个微服务的 confirm 方法;
- 当任意一个方法 try 失败(资源不足、网络异常、代码异常等等),由事务管理器调用每个微服务的 cancle 方法对全局事务进行回滚;
- 按照 TCC 的协议,Confirm 和 Cancel 是只返回成功,不会返回失败。如果由于网络问题,或者服务器临时故障,那么事务管理器会进行重试,最终成功。
优缺点
优点:相对于 2PC 较为简单,Try 操作可以灵活选择业务资源的锁定粒度
缺点:Canfirm 和 Cancel 的幂等性很难保证;
TCC 属于应用层的一种补偿方式,对业务侵入性比较大,耦合性高;
开发成本很高,适用场景十分有限。
2.3 本地消息表
本地消息表的核心思想是将分布式事务拆分成本地事务进行处理,是 eBay 架构师 Dan Pritchett 提出的分布式系统一致性问题的解决方案,也是业界使用最多的。
其流程图如下:
消息生产方需要在业务数据库中额外创建一个消息表,并记录消息发送状态,业务数据与消息在同一事务中提交。事务提交后,消息会经过 MQ 发送给消费方,如果消息发送失败,会进行重试发送。
消息消费方处理 MQ 发来的消息,如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
优缺点
优点:一种非常经典最终一致性实现方案,实现逻辑简单,开发成本⽐较低。
缺点:与业务场景绑定,⾼耦合,不可公⽤;
本地消息表与业务数据表在同⼀个库,占⽤业务系统资源,量⼤可能会影响
2.4 MQ(非事务消息)
通常使用非事务消息支持的 MQ 产品,我们很难将业务操作与对 MQ 的操作放在一个本地事务域中管理。以“转账”为例,始终没有办法保证在扣款完成后,对方账户增加的 MQ 消息投递一定能成功。
我们来分析下可能的情况:
正常的流程:本地操作成功,MQ 投递消息也成功 ;
异常流程:本地操作失败,不会向 MQ 中投递消息了;
本地操作成功,MQ 消息投递时失败,抛出了异常,本地操作回滚;
目前来看,这些情况都没有一致性问题。
那么:若本地操作成功,MQ 投递消息也成功,但 MQ 消费方消费失败的情况该如何处理?
消息不能失效或者丢失,需要保证消息与业务操作一致,
主流的 MQ 产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制的。
避免消息重复消费,即使重复消费也不能因此影响业务结果。
保证消费者接口幂等性;保存消费日志或消费状态表,便于判断;
但有一种场景,消费方程序异常或因异常数据引起的程序异常,即使重试多次仍会失败的场景,仍无法保证系统间的一致性,这种情况只能通过设置预警人工订正等其他补偿方式来解决。
优缺点
优点:性能和吞吐量是优于使用关系型数据库消息表的方案
如果 MQ 自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的
缺点:无法完全保证系统间的一致性
2.5 MQ 事务消息
目前一些主流的 MQ 比如 RabbitMQ 和 Kafka 都不支持事务消息,Apache RocketMQ 在 4.3.0 版中已经支持分布式事务消息,采用了 2PC(两阶段提交)+ 补偿机制(事务状态回查)的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如下图所示。
2.5.1 事务消息流程
正常事务消息的发送及提交
生产者发送 half 消息(半消息 )到服务端;
服务端将消息持久化之后,给生产者响应消息写入结果(ACK 响应);
生产者根据发送结果执行本地事务逻辑(如果写入失败,此时 half 消息对业务不可见,本地逻辑不执行);
生产者根据本地事务执行结果向 Broker 服务端提交二次确认( Commit 或是 Rollback ),Broker 服务端收到 Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;Broker 服务端收到 Rollback 状态则删除半事务消息,订阅方将不会接收该消息;
事务消息的补偿流程
在网络闪断或者是应用重启的情况下,可能导致生产者发送的二次确认消息未能到达服务端,经过固定时间后,服务端将会对没有 Commit / Rollback 的事务消息( pending 状态的消息)进行“回查”;
生产者收到回查消息后,检查回查消息对应的本地事务执行的最终结果;
生产者根据本地事务状态,再次提交二次确认给服务端,然后服务端重新对半事务消息 Commit 或者 Rollback;
2.5.2 事务消息原理
设计思想:RocketMQ 事务消息最主要的特点是:
一阶段发送的 Half 消息对用户是不可见的
事务回查机制
那么如何做到写入消息但是对用户不可见呢?
RocketMQ 事务消息的做法是:如果消息是 half 消息,将备份原消息的主题与消息消费队列,然后改变主题为 RMQ_SYS_TRANS_HALF_TOPIC ,由于消费组未订阅该主题,故消费端无法消费 half 类型的消息。
如何实现事务回查?Broker 会启动一个消息回查的定时任务,定时从事务消息 queue 中读取所有待反查的消息。针对每个需要反查的半消息, Broker 会给对应的 Producer 发一个要求执行事务状态反查的 RPC 请求,然后根据 RPC 返回响应中的反查结果,来决定这个半消息是需要提交还是回滚,或者后续继续来反查。最后提交或者回滚事务,将半消息标记为已处理状态。
优缺点
优点:
- 系统解耦:消息之间独立存储,系统之间消息完成事情
- 复杂度低,实现较为简单
缺点:
- 一次消息需要发送两次请求( half 消息+ Commit or Rollback )
- 业务中需要实现消息状态回查接口
- 目前主流 MQ 中只有 RocketMQ 支持事务消息
2.6 Seata
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT 、TCC 、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
AT 模式是无侵入的分布式事务解决方案,用户只需关注自己的“业务 SQL ”,用户的 “业务 SQL ” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作,适用于不希望对业务进行改造的场景,几乎 0 学习成本。
TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作。事务发起方在一阶段执行 Try 方法,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。
XA 模式是分布式强一致性的解决方案,但性能低而使用较少。
在我们之前的文章中已详细讲过 Seata 相关内容,感兴趣的朋友可以点击查看:
https://mp.weixin.qq.com/s/3dNSF1MErKKkamYEYJhylQ
小结
综上所述,分布式系统的事务一致性本身就是一个技术难题,其主要难点在于网络通信的不可靠。以上介绍的所有方案均有其优缺点和各自适用的场景,目前也没有一种很简单很完美的方案能够应对所有场景。只能通过“确认机制”、“重试机制”、“补偿机制”等各方面来解决一些问题。在综合考虑可用性、性能、实现复杂度等各方面的情况上,比较好的选择是“异步确保最终一致性”,只是具体实现方式上有一些差异。
参考:
分布式系统的事务处理( https://coolshell.cn/articles/10910.html )
分布式系统事务一致性解决方案( http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency )
常用的分布式事务解决方案介绍有多少种?( https://www.zhihu.com/question/64921387/answer/225784480 )
https://blog.csdn.net/weixin_48563689/article/details/109136478
https://www.cnblogs.com/peteremperor/p/13551408.html
更多思考
本篇只是对分布式事务提出了几点可行的解决方案,更多关于分布式事务的内容大家可以阅读以下内容: