关于系统间数据一致性(跨进程事务)的解决方案

问题背景

  • 前台(浏览器或app等)提交一个请求到A系统,A系统调B系统创建订单,同时A系统需要扣除金币(数据库操作)。这是一个跨进程事务,需要保持两个系统的数据一致性。
  • 如果数据都保存在B系统,则没有系统一致性问题,但通常由于业务需要,尤其是系统拆分之后,经常需要处理分布式一致事务问题。

问题分析和方案

系统调用的结果

  • 调B系统可能出现以下三种结果:
    1. 成功
    2. 失败
    3. 超时(未响应)

方案一

  • A系统把调B系统创建订单(调用成功)和扣除金币(数据库操作成功)绑定成进程内的一个事务。
造成数据不一致的原因有:
  1. 调B系统超时(上述第3种情况)。发生的原因通常是网络问题。 这种情况一般A系统当失败处理,导致A系统未扣除金币但B系统已经有订单。
  2. 调B系统成功,但A系统刚好宕机(或重启,断电等原因)。导致跟结果跟1一致。
解决方案
  • B系统提供接口供B系统获取订单结果。(确认机制)
  • 定时同步B系统所有订单数据(这些记录在A系统都是读操作),同步的机制可以是增量的(按照时间段同步),同步结果对比金币的扣除情况,若不一致,则需要进行扣除。

方案二

  1. 解耦,异步任务处理。由原来同步调B系统,变成异步调B系统。将A系统扣除金币和调B系统创建订单,这两个操作分开执行。
  2. A系统接收前台请求之后,先扣除金币,然后生成一个调B系统创建订单的任务(任务落地,状态为待执行)。这两个动作都是数据库操作,同一个数据库事务。
  3. 后台定时取待执行的任务调B系统创建订单,包含重试机制和策略。
注意点
  1. 异步调B系统有可能出现失败的情况,因为B系统提供的接口可能是具有时效性的,或者进行了严格的参数校验。所以需要了解B系统接口的校验规则,在前台提交请求时做好校验,提高后续异步调B系统的成功率。
  2. 在异步调B系统失败的情况下,需要对金币进行回退,并把用户创建订单的状态由执行中置为失败。

总结

  • 以上两种方案对B系统的要求有,创建订单接口需保证幂等性,可以返回相应的错误码。实现幂等性可以使用业务中的唯一标志,或者UUID来进行。
  • 两种方案都可以保证数据的最终一致性,但如果业务上无太硬性要求,且不一致的发生概率较低的情况下,可以使用调接口报错以及数据监控告警,双方线下确认,通过相应补偿机制来解决。
  • 由前台提交创建订单防止重提交 (UUID,数据库索引,集中式cache)

引入第三方组件

notify

  • 引入异步消息队列解耦,相当于上述方案二的升级版。组件具有相关的确认机制,如淘宝的notify。
  • notify使用两阶段提交的机制(预提交和确认提交)。若notify在一定的时间内未收到确认提交请求(如A系统执行完扣除金币动作后,在发出确认请求到notify之前挂掉),则会回调A系统提供的check接口进行确认(A系统需要实现check接口,决策消息commit还是rollback)
  • 淘宝的消息中间件:https://segmentfault.com/a/1190000003059871

其他

除了notify,另一种消息队列的实现:
http://mp.weixin.qq.com/s/x9IRp4-1N4otIVBEEIE-og
http://mp.weixin.qq.com/s/h74d6LtGB5M8VF0oLrXdCA
http://mp.weixin.qq.com/s/Brd-j3IcljcY7BV01r712Q

总结

为了尽量保证消息必达,架构设计方向为:
(1)消息收到先落地
(2)消息超时、重传、确认保证消息必达


一种提高微服务架构的稳定性与数据一致性的方法:http://mp.weixin.qq.com/s/ROVuCPr2Rg3G1m_daYu-Vg

同步转异步,解决稳定性问题

在平时的时候,都是 RPC 同步调用。如果调用失败了,则自动把同步调用降级为异步的。消息此时进入队列,然后异步被重试。所以处理下游依赖就变成了三种可能性:

  1. 完全强依赖,下游不能挂。
  2. 因为我的返回值依赖了某个下游的处理结果,我必须同步调用它。但是不是强依赖,可降级。降级时不返回这部分的数据。同步调用降级时转为异步的。
  3. 完全异步化。下游服务只是消费我写入的队列,我不与之直接RPC通信。

你可能感兴趣的:(关于系统间数据一致性(跨进程事务)的解决方案)