分布式事物设计与实践
数据一致性定义
- 任何人
- 任何时间
- 任何地点
- 任何接入方式
- 任何服务
- 数据都是一致的
数据不一致产生的原因
- 数据分散在多处
- 多个DB
- DB和缓存
- 二手交易平台案例
- 用户,交易,商品等功能
分布式事物产生的原因
刚开始是一个单体进程
经过演变,单体式服务演变成微服务,每个服务都是单独的进程
在用户请求量大的时候,为了缓解数据库的压力,添加了分布式缓存
分布式事物案例
电商平台购买商品
下单->减库存->支付
这就是分布式事物问题,当APP要买东西,这个操作会涉及到多个服务,意味着要操作多个数据库,这样本地事物就无法保证数据的一致性,所以就产生了分布式事物问题.
分布式事物场景
- 电商下单场景
- 下单
- 发送消息到MQ
- 一致性保证
- 本地事物
- 下单操作
- 发送MQ消息操作
- 放进一个本地事物
上述做法有什么问题?
问题:如果发送消息超时了,你是不知道MQ的返回结果是成功和失败的,,timeout这操作不是一个原子的
分布式事物分类
- 刚性分布式事物
- 强一致性
- XA模型
- CAP
- CP
- 柔性分布式事物
- 最终一致性
- CAP,BASE理论
- AP
刚性分布式事物
满足传统事物特性
ACID( Atomicity-原子性, Consistency-一致性,Isolation-隔离性,Durability-持久性)
XA模型
- XA是X/Open CAE Specification(Distributed Transaction Processing)模型中定义,XA规范由AP,RM,TM组成
- 其中应用程序(Application Program简称AP),AP定义事物边界(定义事物开始和结束)并访问边界事物内的资源
- 资源管理器(Resource Manager简称RM),RM管理计算机共享的资源,资源及数据库等
- 事物管理器(Transaction Manager,简称TM),负责管理全局事物,分配事物唯一标识,监控事物的执行进度,并负责事物的提交,,回滚,失败恢复等
2PC(两阶段提交-XA规范标准实现)
- 案例
- 组织爬山
- 过程
- 二阶段提交,是XA规范的标准实现
- TM发起prepare投票
- RM都同意后,TM再发起Commit
- Commit 过程出现宕机等异常,节点服务重启后,根据XA recover 再次进行commit补偿
- 缺点
- 同步阻塞模型
- 数据库资源锁定时间过长
- 全局锁(隔离级别-串行化),并发低
- 不适合长事物场景
柔性分布式事物
- CAP
- 分布式环境下P一定需要,CA权衡折中
- BASE理论
- Basically Available-基本可用
- Soft state 柔性状态
- Eventual consistency 最终一致性
- 架构思考
- 柔性事物是对XA协议的妥协,他通过降低强一致性要求,从而降低数据库资源锁定时间,提升可用性
- 架构经典实现
- TCC模型
- Saga模型
TCC模型
- Try-confirm-cancel
- TCC模型完全交由业务实现,每个子业务都需要实现Try-Confirm-cancel接口,对业务侵入大
- 资源锁定交由业务方
- try
- 尝试执行业务,完成所有检查,预留必要的业务资源
- confirm
- 真正执行业务,不再做业务检查
- Cancel
- 释放Try阶段预留的业务资源
- 案例
- 汇款服务,收款服务案例
- A用户向B用户汇款500元
- 汇款服务
- try
- 检查A账户的有效性,及查看A账户的状态是否为"转账中"或者"冻结"
- 检查A账户余额是否充足
- 从A账户中扣减500元,并将状态设置为转账中
- 预留扣减资源,将从A往B账户转账500元这个事件存入消息或者日志中
- confirm
- 不做任何操作
- cancel
- A账户增加500元
- 从日志或者消息中,释放扣减资源
- 收款服务
- try
- 检查B账户是否有效
- confirm
- 读取日志或消息,B账户增加500元
- 从日志或者消息中,释放扣减资源
- cancel
- 不做任何操作
Saga模型
- 起源于1987年Hector & Kenneth发表的论文Sagas
- Saga模型把一个分布式事物拆分为多个本地事物,每个本地事物都有相应的执行模块和补偿模块(对应TCC中的confirm和cancel)
- 当Saga事物中任意一个本地事物出错时,可以通过调用相关的补偿方法恢复之前的事物,到达事物最终一致性
- 当每个Saga子事物T1,T2,....TN都有对应的补偿定义C1,C2,....CN-1,那么Saga系统可以保证
- 子事物序列T1,T2,.....TN得以完成(最佳情况)
- 或者序列T1,T2,...TJ,CJ-1,..., C2,C1,0<J<N,得以完成
- Saga隔离性
- 业务层控制并发
- 在应用层加锁
- 应用层预先冻结资源等
- Saga恢复方式
- 向后恢复,补偿所有已完成的事物,如果任意子事物的失败
- 向前恢复,重试失败的事物,假设每个子事物最终都会成功
刚性分布式事物VS柔性分布式事物
我们如何实践
- 问题通用解决思路
- 解决这个问题本身
- 让问题本身消失
- 圆珠笔笔芯漏油解决
- 圆珠笔笔芯在写2W次就开始漏油,如果要解决这个问题本身,那么就是加入更好的材料,更高端的技术,如果是让问题本身消失呢,就是固定一个次数,让它只能写1.5W次就没油开始丢弃,这样的两种办法
- 首选是让问题本身消失,次选是解决这个问题本身
- 方案一:从业务场景消除分布式事物
- 思路:核心业务先处理,其他业务异步处理
- 方案二:柔性分布式事物
柔性分布式事物实践
- 通用处理思路
- 本地事物-->短事物
- 分布式事物-->长事物
- 转变成多个短事物
- 案例
- A[下单]->B[减库存]->C[支付]
- A->DB1
- B->DB2
- C->DB3
- A/B/C都成功
- A/B成功,C失败
- 补偿
- 业务场景
- 异步场景
- 基于MQ消息驱动分布事物
- 同步场景
- 基于异步补偿分布
异步场景分布式事物设计
异步场景
商品交易
下单,支付
方案一:业务方提供本地操作成功回查功能
- 事物消息:MQ提供类似X/Open XA的分布式事物功能,通过MQ事物消息能达到分布式事物的最终一致
- 半消息:暂不投递的消息,发送方已将消息成功发送到了MQ服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成"暂不能投递"状态,处于该种状态下的消息即半消息
- 消息回查:由于网络闪断,生产者应用重启等原因,导致某条事物消息的二次确认丢失,MQ服务端通过扫描发现某条消息长期处于半消息时,主要主动向消息生产者询问该消息的最终状态(Commit或Rollback),即消息回查
- MQ分布式事物设计方案
- MQ分布式事物消息设计
- MQ事物消息设计事物消息作为一种异步确保型事物,将两个事物分支通过MQ进行异步解耦,MQ事物消息的设计流程同样借鉴了两阶段提交理论,整体交互流程如上图
- 事物发起方首先发送prepare消息到MQ
- 在发送prepare消息成功后执行本地事物
- 根据本地事物执行结果返回commit或rollback
- 如果消息是rollback,MQ将删除该prepare消息,不进行下发,如果是commit消息,MQ会将消息发送给consumer端
- 如果执行本地事物过程中,执行端挂掉,或者超时,,MQ服务器端将不停的询问producer来获取事物状态
- consumer端的消费成功机制有MQ保证
- 成本:
- MQ需要支持半消息
- MQ需要提供消息遍历
- 业务方需要提供回查接口
- 业务方接入步骤
- 优点
- 通用
- 缺点
- 业务方需要提供回查接口,对业务侵入大
- 发送消息非幂等
- 消费端需要处理幂等
方案二:本地事物消息表
- 本地操作和发送消息通过本地事物强一致性
- 本地事物操作表
- 本地事物消息表
- mqMessages(msgid,content,topic,status)
- 发送端消息不幂等
- At least once (最少发一次)
- Once Only (只发一次)
- At more once(最多发一次)
- 消费端处理消息幂等
- 分布式锁
- A->B->C
- A/B成功,C失败
- 记录错误日志
- 报警
- 人工介入
- 优点
- 业务入侵小
相比于提供消息回查接口(RockectMQ)来说,实际异步场景还是本地消息事物表使用的比较多
同步场景分布式事物设计
- 同步场景
- 首页推荐商品列表
- 商品信息
- 用户信息
- 社交信息
- 购买商品
- 下单->A
- 减库存->B
- 支付->C
通过业务逻辑层驱动
- 解决方案
- 基于异步补偿的分布式事物
- 架构设计的三大关键点
开始记录调用请求的参数,如果失败后基于参数做补偿接口,接口需要保证幂等性
- 总体架构设计
场景:A下单,B减库存,C支付,在调用接口的时候,A先走Proxy存入事物ID,状态,参数等信息,然后执行本地事物,接着B,C走同样的流程如果都成功,那么事物状态改成2,也就是成功,如果在C失败的时候可以更具参数,事物ID对A,B进行补偿
业务逻辑层Proxy设计(基于AOP实现)
- 逻辑层调用上加上事物注解@Around("execution(**(..)) && @annotation(TX)")
- Proxy在真正业务逻辑被调用之前,生成一个全局唯一TXID标示事务组,TXID保存在ThreadLocal变量中,方法开始前写入,完成后清除,并向远端数据库写入TXID并把事务组制成开始状态
- 业务逻辑层调用数据访问层之前,通过RPCProxy代理记录,当前调用请求参数
- 如果业务正常,调用完成后,当前方法的调用记录删除或者存档
- 如果业务异常,查询调用链反向补偿
- 数据访问层设计
- 原子接口
- 补偿接口
- 谁来提供?
- 业务方提供
- 幂等性保证
- 采用本地资源锁,锁定唯一资源
- 基于原则接口方法,在方法名加注解标注补偿方法名
- @Compensable(cancelMethod = "cancelRecord")
- 分布式事物补偿服务
- 事物组表(数据库表TDB)
- 记录事物组状态
- txid state timestamp
- 事物调用组表(数据库表TDB)
- 记录事物组内的每一次调用,以及相关参数
- txid actionid callmethod pramatype params
- 补偿策略
- 调用执行失败,修改事物组状态
- 分布式事物补偿服务异步执行补偿
分布式事物成功案例
- 二手交易创建订单事务组正常流程
- 锁库存->减红包->创建订单
- 代理层透明记录调用请求参数
- 记录事物域的开始与结束
- 在所有远程调用成功时
- 对业务逻辑不做侵入
分布式事物失败案例
- 二手交易创建订单事务组异常流程
- 微服务数据访问层失败,代理更改事务组状态
- 微服务业务正常执行
- 事物补偿服务异步执行补偿
好了,到这里分布式事物也就写完了..休息一下,,哎,又到了找工作的时候了,有需要可以联系我