vlambda博客
学习文章列表

记一次分布式事务问题的解决实践

        近年来,分布式架构越来越流行。分布式事务一致性问题也在困扰系统的开发人员。一旦发生数据一致性问题,数据修复非常麻烦。而且数据不一致容易误导客户,容易使得客户重复操作。
本文记录了一个具体的事务一致性问题解决方法,如果有更好或者是改进的方案也欢迎大家讨论。

问题背景

       一个非常简单的收单系统是这样子的,一个请求收到之后的正常和异常流程如下:

处理流程
序号
正常流程
异常流程(数据不一致)
1
客户通过浏览器请求到A系统
客户通过浏览器请求到A系统
2
A系统处理逻辑,并写入数据库(事务未提交)
A系统处理逻辑,并写入数据库(事务未提交)
3
A系统调用B系统
A系统调用B系统,发生超时错误
4
B系统处理逻辑,写入数据库
B系统处理逻辑,写入数据库
5
B系统将处理结果返回给A系统,A系统提交事务
A系统发生数据回滚
6
返回正常结果给客户
返回异常结果给客户
结果
处理逻辑结束: AB两个系统支持完成逻辑处理,数据一致
处理逻辑结束: A系统异常,数据没有写入,B系统正常写入数据,完成处理逻辑
B系统收到的QPS请求超过设计容量,请求发生拥堵。 但是A系统还是源源不断的收到请求,转发给B系统,B系统的拥堵程度进一步加剧,从而发生大量的数据不一致错误。 客户无法在A系统上查询到数据,以为没有下单成功,实质有一部分数据已经在B系统中了

方案选择

分布式事务按照事务一致性的程度分为两种情况,一种要求数据在任何情况下都强一致性,属于刚性事务。 另外一种情况可以容忍数据在短时间内不一致,但是要求最终达到数据一致性,属于柔性事务。

方案一: 采用2PC(XA)协议。 网络上关于2PC的介绍已经非常非常多了,2PC也是一种比较成熟的方案,2PC方案也可以保证数据强一致性。 网上所有的方案几乎都会说到2PC方案的两个缺点:
  • 方案实现复杂

  • 效率会大幅下降

这两个缺点都是比较大的缺点,尤其是第二条。 一个功能如果很影响系统吞吐量,基本上是肯定不可以上线的(但是我也没有实际测试过,具体会多影响)。

方案二,参照柔性事务的概念,将处理流程改成如下:

主流程

1) 客户将请求发送到A系统

2) A系统处理逻辑,并落库且提交数据库事务,将订单标注为处理中

3) A系统返回处理请求给界面程序,同时将请求发送到MQ

4) B系统从MQ上消费消息,并处理逻辑和完成写库操作

5) 处理完成之后,发送处理结果回MQ

6) A系统将MQ上的B系统的处理结果消费掉,更新一开始的订单的状态

分支流程

1) C系统每隔一定时间(5秒钟)扫描过去一段时间(3分钟)内的订单,检查A系统中是否有未知和失败的订单,发起重试(不经过MQ,以RPC的形式直接调用)

同时系统做如下修改
  • A和C系统可能对未知订单同时发起状态更新,需要加锁

  • B系统通过对jetty线程的控制做限流处理,将无法完成的请求缓存在jetty队列,如果队列已经满了就直接返回:HTTP-Code=429,too many request的返回。这样子可以防止B系统被流量压垮

  • 浏览器同步等待n秒(5秒),同时轮询订单结果。如果n秒内订单有明确结果,就同步呈现给客户;如果是n秒订单还是状态未知,就异步返回,同时页面显示“订单正在处理中,请稍后”

  • 系统使用的是ActiveMQ。数据使用DB持久化,消息事务开启的情况下,生产者最大的QPS大概是1万多一点,消费者的QPS大概是生产者的1/20。MQ与DB、MQ与应用之间的网络带宽影响很大。满载的时候,CPU的负载大概是80%左右,消息体大小对ActiveMQ的影响不大…(具体的机器性能没有记录下来…-_-||)

这样修改带来的好处
  • 通过MQ系统解耦,对订单流量进行削峰平谷

  • 只有明确的业务错误才不会发起重试,对例如网络抖动,锁竞争发生的错误,C系统可以通过重试下单成功

这样修改造成的缺点
  • 增加了系统的复杂度

  • 客户可能看到“订单正在处理中”的情况,无法及时获得订单信息

总结

性能和一致性是互相妥协的结果…,没有最好的方案,只有最(比较)适合自己的方案…