时间:2021年11月14日
作者:小蒋聊技术
大家好,欢迎来到小蒋聊技术。小蒋准备和大家一起聊聊技术的那些事。
如何实现分布式事务这个话题,想必大家已经都不陌生了。小蒋和大家已经聊了3种不同的实现方法了。这些不同的实现方法都有各自的特点和不足,没有哪种方法是十全十美的。
架构师,必须根据具体的项目情况,选择出哪种方案是最棒的。最棒,并不意味着方案会给项目带来什么巨大的优势,也有可能是选择出一个对项目影响最小的方案。
架构师,要选择出一个最“合适”的方案。这样说或许更贴切一些。
今天小蒋带来的分布式实现方案,应该是目前行业里比较主流的一种实现方案,使用场景也是比较多的:“本地消息表 & 可靠消息”
本地消息表 & 可靠消息
有人不禁会问,为什么是两个方案?
因为,“本地消息表” 和 “可靠消息”,他们实现分布式事务的思路其实是一样的,只是实现技术上的差异。
小蒋个人理解,“可靠消息”这个方案,它实际上是对“本地消息表”的一种封装。
咱闲言少叙,先一起来看看“本地消息表”这个方案,它最初是由ebay架构师Dan Pritchett在2008年发布一篇文章中提出的。(In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability)
此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
小蒋的理解是,其核心就是通过中间件(本地文本、数据库、消息队列)把“大事务”转变为多个“小事务”。
咱们还是拿“抖音平台提现到支付宝平台”这个例子,来看看究竟它是如何实现的:
通过真实的案例,我们看到“本地消息”是如何实现分布式事务的了。“写本地消息”和“业务操作”放在了一个事务里,保证了“业务”和“发送消息”的原子性,他们要么全部成功,要么全部失败。
异常处理&容错机制:
- “扣减余额事务”失败时,事务直接回滚,无后续步骤。
- “轮询生产/消费消息”失败 or “增加余额事务”失败,都会进行重试。
- “增加余额事务”重试达到上限后仍“未成功”,需要通过消息队列向“抖音平台”发起“事务回滚”操作或“人工”进行干预。
“本地消息表”特点:
- “大事务”可以被分拆成多个“小事务”,方便业务实现。
- 生产者需要额外的创建消息表。
- 每个本地消息表都需要进行轮询。
- 消费者的逻辑如果达到重试次数仍“未成功”,需要更多的容错机制,来保证回滚操作。
总结:
“本地消息表方案”遵循BASE理论,采用的是 “最终一致性”。比较适用于可异步执行的业务,且后续操作无需回滚的业务。
因为“异常处理”和“容错机制”需要“重试”,所以在接口实现上需要支持“幂等”。
小蒋个人认为“本地消息表方案”是目前这几种方案里面比较适合实际业务场景的,它不会出现“两阶段提交”那样复杂的实现(当调用链很长的时候,“两阶段提交”的可用性比较低),也不会像TCC那样,可能出现“无法确认”或者“无法回滚”的情况。
在支付场景,使用“本地消息表方案”,通常会有“人工重试”机制。一般是由“对账系统”事后来进行触发,通过“对账系统”保证账务精准和安全。
可靠消息
了解了“本地消息表方案”,我们再来看“可靠消息方案”就容易理解的多。
有些第三方MQ是支持事务消息的,比如RocketMQ,他支持事务消息的方式也是类似于采用的二阶段提交。但是,主流的MQ都是不支持事务消息的,如RabbitMQ和Kafka。
大致流程如下:
- 抖音平台先向mq发送一条prepare消息,如果prepare消息发送失败,则直接取消操作。
- 如果消息发送成功,则执行本地事务。
- 如果本地事务执行成功,则向mq发送一条confirm消息。如果失败,则发送回滚消息。
- 支付宝平台定期消费mq中的confirm消息,执行本地事务,并发送ack消息。如果支付宝平台中本地事务失败,会一直不断重试,如果是业务失败,会向抖音平台发起回滚请求。
- mq会定期轮询所有prepared消息调用抖音平台提供的接口查询消息处理情况,如果该prepared消息本地事务处理成功,则重新发送confirm消息,否则直接回滚该消息。
接下来,小蒋将带着大家一起看看,投递方式不同,业务将会出现怎样不同的异常结果。
消息投递过程:方式一
- 开启本地事务
- 扣减100余额
- 投递消息到MQ
- 提交本地事务
这种方式是将发消息放在了事务提交之前。
可能存在的问题:
第3步发生异常:导致第四步失败,转账业务失败。
第4步发生异常,其他步成功:“抖音平台”转账业务失败,消息投递成功,“支付宝”转账业务成功。
消息投递过程:方式二
下面我们换一种方式,将发送消息放到事务之后进行。
- 开启本地事务
- 扣减100余额
- 提交本地事务
- 投递消息到MQ
可能存在的问题:
第4步发生异常,其他步成功:导致“抖音平台”转账业务成功,投递消息失败,“支付宝平台”未收到转账消息。
以上这两种方式是比较常见的做法,在没有健全容错机制的情况下,也是最容易出错的。所以,即时使用“可靠消息”方案,也必须要考虑“异常处理”和“容错机制”。
“本地消息表” VS “可靠消息”
接下来,小蒋来和大家一起对比一下两种方案。
“可靠消息”方案也是采用的“最终一致性”。
对比“本地消息表”方案,“可靠消息”方案不需要建立消息表,不用依赖本地数据库事务,适用于高并发的场景。
“异常处理”与“容错机制”两种方案基本一致。
目前市场上面实现该方案的只有阿里的RocketMQ,RocketMQ事务消息部分的代码目前也未开源。
以上,是今天“小蒋聊技术”技术部分的全部内容。
“靠谱”方案的重要性
如果你现在是一名工程师,将来想要成为一名架构师。或者你已经是一名架构师了,还想再进一步提升。接下来,小蒋准备为大家分享一些自己工作中的心得和体会。
上周,小蒋参加了公司的内部培训。取得了小组第一的成绩,但是,小蒋一点都不开心,因为是倒数第一。
上周公司组织了一场模拟经营的培训,初始资金是530万。10分钟为一个周期,每个周期的经营费用为20万,其中包括员工工资,场地,税费,等一些列经营费用。
模拟经营,一共22个周期,加上经营前的各种准备活动,一共4个小时左右。
模拟经营中,有公司总经理、销售经理、生产经理、采购经理、财务经理,5个角色,小蒋担任的是财务经理。
模拟经营开始后,每个人都忙得不亦乐乎,中间连喝水大家都是用跑来完成的,可见时间的宝贵。
但是,整个经营活动结束后,小蒋作为财务经理,一盘点不但没有任何盈利,反而还亏损了260万,我滴天啊!
看着销售经理往回不停的拿销售单,销售经理那可以说是一脸的兴奋,这些销售单可都是销售提成啊!
但是,因为原料短缺,生产效率低等问题,这些销售单无法按时交货,公司不停的被罚款,生产经理漏出的是满脸的无奈。
最后,因为自由现金流不足,为了不让公司倒闭破产,不得不找银行进行了借贷,公司才勉强撑住了。
钱到哪里去了?
小蒋后来做盘点的时候,算了一笔账。如果我们团队拿着530启动资金,什么都不干,就在那等着。
530启动资金 – (20万管理费/周 * 22周) = 90万
但是,我们每个人忙得不亦乐乎,连喝水的时间都没有,结果却是不但没有剩下钱,还欠了下了260万负债。
销售经理,兴高采烈的拿着销售单,以为是盈利。结果未来等着他的却是无法按时交货而导致得罚款。你以为的,根本就不是你以为的。
一个靠谱的方案,有多重要!
架构师,你就是那个出方案的人。如果你在做方案的时候,不想着自己团队的具体情况,不考虑现在公司资源的情况,那你做的那个方案根本就“不靠谱”。
看着全是新feature,欢天喜地的。结果却是方案根本无法“落地”,还把团队拉进了鬼门关。
所以,小蒋作为一名架构师,通过公司的这次培训,深深的感受到了方案“靠谱”的重要性。
写在最后
方向和目标一定要正确,如果方向和目标错了,你越努力错的就越多!
年龄的增长不可怕,可怕的是从未成长!
感谢大家支持小蒋,小蒋希望和大家共同成长,谢谢。
音频版本:【20211114】在技术上是如何实现分布式事务_V4https://www.ximalaya.com/keji/51588599/472325594