前言
作为一个还没有正式工作的学生,分布式的真正场景我还没有接触过,所以这篇博客完全作为理论知识的学习,也就是在网上寻找各种介绍和问题的解释,写个总结类的博客,也当做一个学习的过程
事务
事务的概念是我在学习数据库的时候接触的,本地事务相对而言还是比较简单的,前有数据库本身对ACID的保证,后有各大框架对事务的封装,不论是理解还是使用起来都是比较简单的,我之前有一篇在CSDN的关于事务的博客,点这里,有时间转过来
相比而言,分布式事务就要难很多了,主要是在微服务、SOA等服务架构被使用后,一个项目被分为多个服务,而一般而言,每个服务都拥有自己的数据库,即时我们不使用微服务的架构,当单个项目的数据量过于大的时候,一个数据库肯定扛不住,这个时候需要分库分表,再说不需要分库分表,我们也会遇到使用缓存的情况,DB和缓存的一致性问题。
这些都属于分布式事务的场景,是福不是祸,躲不过就要拿下它。
分布式事务基础
ACID是本地事务的基础,那么分布式事务的基础是什么呢?有两个,CAP和BASE,分别介绍一下:
CAP理论
- C : 一致性,指在分布式系统中的所有数据备份,是否在同一时刻拥有相同的值,也就是客户端操作完成后,所有节点的数据保持完全一致,这里是指强一致性
- A : 可用性,指在服务一直可用,而且是正常的响应时间
- P : 分区容错性,指在某节点或者网络分区发生故障后,系统整体还是能够向外提供满足条件的服务,这个是分布式的基础,是不能放弃的
CAP选择
- 什么是选择呢,也就是说我们只能在CAP中选择其二,而无法同时满足三个条件
- CA : 不要求P是不行的,上面也说了,P是分布式基础
- CP : 也就是说不要求可用性,但保证了强一致性,不同server之间就需要强制同步数据,这样一旦发生网络故障或者消息丢失,就要牺牲用户的体验,等待数据完全同步再进行提供服务,常见的就是分布式数据库,Zookeeper就是一个CP,即再任何时候对于Zookpeeper的访问请求都能得到一致的数据,这是由zk的作用决定的,我们都知道的是,Dubbo使用zk做注册中心,kafka使用zk做服务的协调.zk的职责就是保证数据在其管辖下的所有服务之间保持同步,一致
- PA : 抛弃了强一致性,保证高可用,为了保证用户的访问可以马上得到回复,但是无法保证全局数据的一致性,常见的场景是业务系统,比如购票系统,经常购买的时候显示是有票的,但是当你输入验证码,下单的时候,才告诉你没有票了,但是是要保证最终一致性的,也就说,虽然下单的瞬间,票的数目可能存在不一致,但是最终票的总量是确定的,不能出现多卖的情况
BASE理论
- BA: 基本可用(Basically Available): 是指分布式系统出现故障的时候,允许损失部分可用性,保证核心可用,比如双11,为了应对访问量激增,部分用户可能会被引导至降级页面
- S : 软状态(Soft State): 是指允许系统存在中间状态,而改中间状态不会影响系统整体可用性,体现在如果我们的数据有多个副本,那么副本之间同步的延时就是软状态的体现
- E : 最终一致性(Eventual Consistency) : 是指系统中的所有副本在一定时间后,都会达到一致的情况
分布式事务协议
XA规范
X/Open组织定义了分布式事务的处理模型,X/Open DTP模型包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)、通信资源管理器(CRM)四部分。一般,常见的事务管理器是交易中间件,常见的资源管理器是数据库,常见的通信资源管理器是消息中间件。 通常把一个数据库内部的事务管理,如对多个表的操作,当做本地事务。分布式事务处理的对象是全局事务,所谓全局事务,是指分布式事务处理环境中,多个数据库需要共同完成一个工作,例如,一个事务可能更新几个不同的数据库,对数据库的操作发生在系统的各处但必须全部被提交或者回滚,此时一个数据库对自己内部多操作的提交不仅依赖本身操作是否成功,还有依赖于全局事务的其他操作是否成功,如果任一数据库操作失败,所有该全局事务的操作必须回滚
2PC 二阶段提交协议
二阶段提交的重点是我们需要引入一个协调者的组件,由他来指示这些节点是否要把操作结果真正的提交。
所谓的二阶段提交是指: 第一阶段: 准备阶段(投票阶段) 和 第二阶段: 提交阶段(执行阶段)
- 准备阶段
事务协调者给每个参与者发送Prepare消息,每个暗语者要吗直接返回失败,要吗在本地执行事务,写本地redo和undo日志,但不提交,到一种万事俱备,只欠东风的状态 - 提交阶段
如果协调这都参与者的失败消息或者超时,直接给每个参与者发送回滚消息,否则,发送提交消息,参与者根据协调者的指定执行提交或者回滚,释放所有事务过程中使用的锁资源 - 缺点
- 同步阻塞: 执行过程中,所有参与的节点都是事务阻塞的,当参与者占用公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态
- 单点故障: 由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去,尤其是第二阶段,
- 数据不一致,在阶段而中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求,发生数据不一致
- 实现
蚂蚁中间件 DTS (distributed transaction service)tech.antfin.com/docs/46887
3PC 三阶段提交协议
三阶段提交协议是二阶段的一个改进版本,与二阶段相比,多了两个改动点:
- 引入超时机制。同时在协调者和参与者中都引入超时机制
- 在第一阶段和第二阶段中插入一个准备阶段,保证在最后提交阶段之前各参与阶段的状态是一致的。
- CanCommit阶段
协调者向请求者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应 - PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作,两种可能:- 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行
- 如果有任何一个No响应,或者等待超时之后,协调者都没有接到参与者则的响应,执行事务的中断,即向所有参与者发送abort请求,参与者收到abort请求之后,执行事务的中断
- doCommit阶段
该阶段进行真正的事务提交,可以分为以下两种情况- 执行提交 , 协调者收到ACK响应,从预提交到提交状态,并向所有参与者发送doCommit请求,参与者收到doCommit,进行事务提交,
- 中断事务, 协调者没有接收到参与者发送的ACK响应,会执行中断事务,但是如果参与者没有即时收到doCommit请求,会在等待超时后,进行事务的提交,这样会减少阻塞,但是增加了
2PC,3PC区别
相对于2PC,3PC主要解决了单点故障并减少了阻塞,但是同时也导致了数据一致性的问题,也就说两种提交方案都无法彻底解决一致性的问题.
TCC
- 将业务分为try/confirm/cancel
- try: 尝试执行,完成业务检查,预留业务资源(一般是在表中添加冻结字段)
- confirm : 确定执行,不做业务检查,只使用try阶段预留的业务资源,confirm需要满足幂等性,可重试
- cancel: 取消执行,释放try阶段的业务资源,满足幂等,可重试
- 参考实现
- byteTCC github
- 一般不用,实现比较复杂
本地消息表
由ebay提出的方案,方案核心是将需要分布式处理的任务通过消息日志的方式来异步执行,消息可以存储在本地,数据库,消息队列。再通过业务规则自动或人工发起重试。
例如跨行转账,用户a向用户b转账,首先系统扣掉a账户的金额,把转账消息写入到消息表里,如果事务执行失败则转账失败,如果成功,系统由定时轮训消息表,往mq中写入转账消息,失败重试,mq消息被消费并往用户b账户中增加转账金额,执行失败并重试。