大规模业务数据的方案通常都是分库分表,并且一些场景会同时跨多个库发生业务。在 "分布式事务概述"一文中,咱们讲到事务消息的MQ补偿方案是目前公认的较为理想的分布式事务解决方案,实施成本也较高,今天咱们即讲述这种补偿方案的最终一致性落地细节。数据库
1、消息补偿流程服务器
回顾以前咱们提到,消息中间件在分布式系统中的主要做用:异步通信、解耦、并发缓冲。基于MQ实现分布式事务一致性是一种异步确保型的实现方案,将同步阻塞的事务变成异步的,避免对数据库事务的争用。大体流程以下:并发
基于消息补偿的分布式事务方案每每用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操做+发消息)+B系统的本地操做,其中B系统的操做由消息驱动,只要消息事务成功,那么A操做必定成功,消息也必定发出来了,这时候B会收到消息去执行本地操做,若是本地操做失败,消息会重投,直到B操做成功,这样就变相地实现了A与B的分布式事务。异步
2、事务补偿应用分布式
2.1 假设业务场景高并发
"假设用户a从本身的余额中向用户c余额中转100元钱。用户a余额存放在A库中,用户c余额存在C库中"工具
因为分库的存在,破坏了事务的原子性,若是没有分布式事务,转帐过程可能会出现以下的问题:性能
第1种状况,应用写队列超时致使重发了消息.那么结果是a原本向c转帐100元.结果却转帐了200元...net
第2种状况,应用将消息成功写入队列,可是队列服务器挂了.结果是a向c转帐失败.日志
第3种状况,中间层(队列的消费者)将消息取出,修改a的帐户余额,可是用户a的库挂了,致使事务失败.结果是a向c转帐失败.
第4种状况,中间层已经成功修改了用户a的帐户余额,可是在修改c用户余额的时候,用户c的数据库挂了。结果是用户a的钱扣了,可是用户c的钱没有增长.
第5种状况.中间层从队列拿到了消息,可是还未及处理,中间层自己挂了..
2.2 基于队列事务的最终一致性解决方法
须要的前置工具或表:
1. 分布式ID生成器
2. transaction_log(tran_id,a,c,money),事务业务日志表
3. message_log(tran_id,account,money),消息日志表
结合"消息补偿流程"中的流程图,整体过程以下:
1. 应用经过ID生成器生成事务id,将本次事务日志写入事务业务日志表,暂不提交。
2. 向队列发送两个消息.一个消息是用户a -100元,另外一消息是用户c +100元,两个消息须要带上第一步获得的tran_id。确保两个消息都成功入队列,则提交业务日志的事务。一旦有任何异常,回滚事务。提交了事务,应用则能够直接返回.提示用户交易完成。
3. 中间层获取消息。先链接用户a的数据库.查询transaction_log表,若是没有该全局事务ID,则不予处理.(确认有这个全局事务,才处理),查询message_log表,若是存在记录,则不予处理.(防止消息超时重发)。开始消费端本地事务.update用户a余额,减100元.再写message_log表,记录本次处理,最后本地提交事务。
4. 中间层链接用户c的数据库,作相同的操做。
5. 一个定时任务,每隔5分钟,检查transaction_log和message_log中是否存在不一致的tran_id.若是有不一致的状况,则进行事务补偿或人工处理。
6. 完成完成转帐。
3、潜在问题
3.1 . 从上述流程来看,将本地事务和发消息放在了一个分布式事务里,须要保证要么本地操做成功而且对外发消息成功,要么二者都失败。实际中,支持这个原子操做的消息中间件并很少(rabbitMq、kafkaMq等都不支持),阿里开源的RocketMQ支持这一特性。
3.2. 随着业务增多,transaction_log、message_log表会比较大,这2张表也须要考虑分库。不然瓶颈会特别大。