zoukankan      html  css  js  c++  java
  • 使用消息系统来解决分布式事务

    本篇文章综合了网上的多篇博客。

    说到分布式事务,就会谈到那个经典的”账号转账”问题:2个账号,分布处于2个不同的DB,或者说2个不同的子系统里面,A要扣钱,B要加钱,如何保证原子性?

    一般的思路都是通过消息中间件来实现“最终一致性”:A系统扣钱,然后发条消息给中间件,B系统接收此消息,进行加钱。

    但这里面有个问题:A是先update DB,后发送消息呢? 还是先发送消息,后update DB?

    假设先update DB成功,发送消息网络失败,重发又失败,怎么办?
    假设先发送消息成功,update DB失败。消息已经发出去了,又不能撤回,怎么办?

    所以,这里下个结论: 只要发送消息和update DB这2个操作不是原子的,无论谁先谁后,都是有问题的。

    那这个问题怎么解决呢??

    错误的方案

    有人可能想到了,我可以把“发送消息”这个网络调用和update DB放在同1个事务里面,如果发送消息失败,update DB自动回滚。这样不就保证2个操作的原子性了吗?

    这个方案看似正确,其实是错误的,原因有2:

    (1)网络的2将军问题:发送消息失败,发送方并不知道是消息中间件真的没有收到消息呢?还是消息已经收到了,只是返回response的时候失败了?

    如果是已经收到消息了,而发送端认为没有收到,执行update db的回滚操作。则会导致A账号的钱没有扣,B账号的钱却加了。

    (2)把网络调用放在DB事务里面,可能会因为网络的延时,导致DB长事务。严重的,会block整个DB。这个风险很大。

    基于以上分析,我们知道,这个方案其实是错误的!

    方案1:使用非事务消息

    假设消息中间件没有提供“事务消息”功能,比如你用的是Kafka。那如何解决这个问题呢?

    解决方案如下:
    (1)Producer端准备1张消息表,把update DB和insert message这2个操作,放在一个DB事务里面。

    (2)准备一个后台程序,源源不断的把消息表中的message传送给消息中间件。失败了,不断重试重传。允许消息重复,但消息不会丢,顺序也不会打乱。

    (3)Consumer端准备一个判重表。处理过的消息,记在判重表里面。实现业务的幂等。但这里又涉及一个原子性问题:如果保证消息消费 + insert message到判重表这2个操作的原子性?

    消费成功,但insert判重表失败,怎么办?关于这个,在Kafka的源码分析系列,第1篇, exactly once问题的时候,有过讨论。

    通过上面3步,我们基本就解决了这里update db和发送网络消息这2个操作的原子性问题。

    但这个方案的一个缺点就是:需要设计DB消息表,同时还需要一个后台任务,不断扫描本地消息。导致消息的处理和业务逻辑耦合额外增加业务方的负担。

    方案2 – 使用RocketMQ 事务消息

    RocketMQ是一个两段式提交协议的实现,其提交过程如下:

    总结:对比方案2和方案1,RocketMQ最大的改变,其实就是把“扫描消息表”这个事情,不让业务方做,而是消息中间件帮着做了。

    至于消息表,其实还是没有省掉。因为消息中间件要询问发送方,事物是否执行成功,还是需要一个“变相的本地消息表”,记录事物执行状态。

    参考文档

    https://www.cnblogs.com/dinglang/p/5679542.html

    http://lifestack.cn/archives/429.html

    http://blog.csdn.net/chunlongyu/article/details/53844393

    http://blog.jobbole.com/89140/

  • 相关阅读:
    java基础5 (一维)数组和二维数组
    Java 内部类
    Java 抽象类和接口
    Java 多态
    Java 继承
    Java 包(package)
    Java String类和StringBuffer类
    Java 数组
    Java 封装与类
    Java 概述和编程基础
  • 原文地址:https://www.cnblogs.com/gudi/p/8185099.html
Copyright © 2011-2022 走看看