大事务拆分项目应用及系统异步化处理-再看分布式事务
⼤事务拆分项⽬应⽤及系统异步化处理-再看分布式事务
Case1
⼀、交易还款业务⼤事务
废话不多说,直接⼀个⾦融还款业务创建交易账单case,还款逻辑如下:
梦见好朋友死了1、⽤户点击还款,发起还款请求
真男22、服务端接收还款请求,进⾏还款计划查询校验
3、进⾏其他还款验证
4、返回⽤户还款计划,⽤户接收后,执⾏还款(调⽤⽀付⽹关等)
5、⽀付⽹关通知交易还款结果成功与否
6、交易更新还款账单状态
黑社会排名整个交易还款操作⽏庸置疑,必须保证事务⼀致性,故上述6步操作均包裹在⼀个⼤事务中进⾏。除了⼤事务包裹导致数据库资源占⽤之外,⽉末还款⾼峰,还款请求密集,db资源响应问题集中凸显。
⼆、解决⽅案
1、数据库事务异步化
简⾔之,将⼤事务拆分成⼩事务,⼩事务间通过MQ等异步机制进⾏通讯。这就涉及到如何进⾏事务拆分,事务1成功,肯定影响事务2的执⾏。例如如果⽀付⽹关执⾏还款成功为事务1,⽽交易更新状态拆分到事务2中,则就有可能出现,MQ经由丢消息,导致交易未接受⽀付成功请求从⽽执⾏换狂账单状态更新。这就是不合理的事务拆分。应当保证⽹关⽀付结果跟最终交易账单状态的原⼦性。也就是说这两部操作得在⼀个事务中。
则上⾯⼤事务可拆分为
1、⽤户点击还款,发起还款请求-事务外
2、服务端接收还款请求,进⾏还款计划查询校验-事务外
3、进⾏其他还款验证-事务外
4、返回⽤户还款计划
以下3步为1个事务中操作逻辑:
5、⽤户接收还款计划,执⾏还款(调⽤⽀付⽹关等)
5、⽀付⽹关通知交易还款结果成功与否
6、交易更新还款账单状态
这样拆分,即将前4步查询操作,拆出⼤事务中。
拆分原则:对必须保持顺序执⾏的服务,按照顺序执⾏。对能够同步执⾏(⽆太多强事务要求)的服务,均采⽤异步处理。以达到增强服务并⾏性,减少响应时间。
如果考虑到出现查询后其他终端进⾏更新操作,导致脏数据,可在查询处进⾏乐观锁等判断处理。乐观锁处理就不会采⽤数据加锁机制,由数据使⽤⽅判断数据过期与否,是否进⾏后续更新等操作。步骤之间,可通过mq进⾏通知,同时利⽤重试机制、mq消息的必达性等进⾏数据最终⼀致性保证。
三、再看⼀致性原理
ACID 强⼀致性与CAP、BASE的最终⼀致性,(柔性事务),
同样结合case对分布式事务⼀致性进⾏理解。
⽤户下单,针对仅有1个库存的商品,AB⽤户都相中,A先浏览加购物车继续浏览其他商品,⽽B⽤户⽴刻对该商品进⾏下单。此时,多个数据库实例中,B下单操作后,实例1进⾏减库存,⽽其他实例同步需要时间,如果A在同步尚未完成时进⾏下单,查询数据库实例2尚有1个库存,此时就出现超卖现象。
分布式系统如何解决这种问题?
在B下单后,只有当DB1\2\3所有数据均同步完成后,才返回B⽤户下单成功,期间不接受其他⽤户对该商品的下单请求(C数据⼀致性得以保证。且分区容错性P由于db的多实例集,1个down掉其他db补上,分区容错也得以满⾜)。⽽此时就涉及服务可⽤性,在数据同步期间,服务对其他⽤户是不可⽤的(A未得以满⾜)。要解决可⽤性A的问题,可将数据仅保留⼀份,⽆集复制同步操作,不存在服务不可⽤时间,则下单服务随时可⽤(A得以满⾜)。但数据库单例节点,挂了服务全完,此时就放弃分区容错性P,保证服务随时可⽤A。
这就是CAP中三者只能满⾜其⼆的实例分析。根据业务需求进⾏取舍。
什么快递可以寄到国外⽽BASE则提出BA基本可⽤、S柔性状态、E最终⼀致性。基本可⽤是指服务例如有4个节点,down调⼀部分,保证还有活的提供服务就⾏。柔性状态即允许中间状态,例如上述3个数据库实例,可存在1、2已完成同步,3数据未同步状态。最终⼀致性即经过⼀段时间后,数据最终保证全同步即可。
Case2
数据也必须要更新成功。
⼀、本地事务,⽀付宝扣1w与余额宝⼊1w,两个update写在⼀个本地事务中。
实现1:sql事务
Begin
transaction
update A set amount=amount-10000 where userId=1;
update B set amount=amount+10000 where userId=1;
End
transaction
commit;
实现2:spring加个注解就搞定
@Transactional(rollbackFor=Exception.class)
2022年几月几号召开public void update() {
updateATable();//更新A表
updateBTable();//更新B表
}
但实际情况是,⽀付宝账户表和余额宝账户表显然属于两个独⽴系统的数据库服务,不会在同⼀个数据库实例上,分布在不同的物理节点上,这时本地事务就⽆法控制。
⼆、分布式事务
1、事务协调器发送prepare告知⽀付宝-1k的请求,同样发送prepare告知余额宝+1k的请求。(prepare消息写到本地⽇志中,保证如果出现故障,在故障后恢复⽤,本地⽇志起到现实⽣活中凭证
的效果)
2、⽀付宝、余额宝返回事务协调器yes,我可以执⾏这条sql,并执⾏具体本机事务,但不commit(同样,yes or no 都写到本地⽇志中)
3、事务协调器接收到均yes,发送⽀付宝、余额宝commit请求,将执⾏结果成功与否返回事务协调器。(⽇志)
任宰范
4、协调器均接受成功,则完事⼉,有任⼀失败,执⾏事务abort回滚。
问题:
两阶段提交涉及多次节点间的⽹络通信,通信时间太长。事务时间相对于变长了,资源锁定时间增长,造成请求等待。不适⽤于⾼并发场景。
三、使⽤MQ解决避免分布式事务
形同吃饭付了钱后,饭店并不会直接把你点的炒肝给你,⽽是给你⼀张⼩票,然后让你拿着⼩票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢?原因很多,其中⼀个很重要的原因是为了使他们接待能⼒增强(并发量更⾼)。
只要这张⼩票在,你最终是能拿到炒肝的。同理,当⽀付宝账户扣除1万后,我们只要⽣成⼀个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终⼀致性。
如何保证消息可靠保存?
1、业务与消息耦合,⽀付宝扣款,同时记录消息数据,这个消息数据与业务数据保存在同⼀数据库实例⾥(本地事务作保证)。执⾏成功后,通知余额宝,余额宝处理成功后发送回复成功消息,⽀付宝收到回复后删除该条消息数据。
2、业务与消息不耦合,抽象出消息模块。
1)⽀付宝执⾏扣-1k,先不commit,同时发送消息给消息模块。
2)消息模块记录,记录成功后,⽀付宝commit本地请求
3)commit结果发给消息模块,commit成功,消息模块发送消息,让余额宝+1k,失败则不发送
4)余额宝+1k后,发送消息模块,增加成功。如果+1k失败,消息模块未收到success请求,则重试,再次发送
如何保证消息不重发?
⽀付宝或消息模块记录消息发送流⽔,发送成功否,余额宝返回结果等。发之前先查⼀遍,如果状态是已执⾏,且接收到余额宝成功结果,则丢弃此条消息发送。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。