分布式事物的设计与实践
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | 彼岸舞
来源 | urlify.cn/VbaYNv
数据一致性定义
任何人
任何时间
任何地点
任何接入方式
任何服务
数据都是一致的
数据不一致产生的原因
数据分散在多处
多个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柔性分布式事物
刚性事物(XA) | 柔性事物 | |
业务改造 | 无 | 有 |
回滚 | 支持 | 实现补偿接口 |
一致性 | 强一致(CP) | 最终一致性(AP) |
隔离性 | 原生支持 | 实现资源锁定接口 |
并发性能 | 严重衰退 | 略微衰退 |
适合场景 | 短事物,并发较低 | 长事物,高并发 |
我们如何实践
问题通用解决思路
解决这个问题本身
让问题本身消失
圆珠笔笔芯漏油解决
圆珠笔笔芯在写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
补偿策略
调用执行失败,修改事物组状态
分布式事物补偿服务异步执行补偿
分布式事物成功案例
二手交易创建订单事务组正常流程
锁库存->减红包->创建订单
代理层透明记录调用请求参数
记录事物域的开始与结束
在所有远程调用成功时
对业务逻辑不做侵入
分布式事物失败案例
二手交易创建订单事务组异常流程
微服务数据访问层失败,代理更改事务组状态
微服务业务正常执行
事物补偿服务异步执行补偿
粉丝福利:Java从入门到入土学习路线图
👇👇👇
👆长按上方微信二维码 2 秒
感谢点赞支持下哈