分布式事务案例分享

293 阅读5分钟

case 1

场景:

某服务向审批中心发起审批请求,要求实现该服务业务逻辑成功,同时成功发起审批。或者同时失败。同时审批服务一旦成功,将发起审批链,向相关人员推送消息,也就是当发起审批成功时,发起方即使发生奔溃,也要保证最终的数据一致性。

1.发起审批  -> 2.审批服务接收请求,响应结果  -> 3.提交/回滚

当发起方成功,审批服务失败,结果应当是发起失败,可重新发起审批。

如果发起方基于本地事务,根据审批接口结果,决定是否提交/回滚,无法应对crash场景。可能存在审批服务成功,本地事务来不及提交就down掉的场景。

实现原理

互相传递唯一编号标识,依赖跑批进行数据纠正

操作:

  1. 发起方只要保证发起审批前的原子性即可,业务逻辑执行成功,发送 唯一编号 uniqueId至审批服务。

  2. 审批服务保存 uniqueId, 一旦成功,回传 唯一编号 processId

  3. 发起方调用审批接口返回后,更新processId至相关表中。如果返回的结果为fail,删除业务表中相关数据

当回传success,本地crash 没来的及保存processId,或者回传fail,没来得及删除业务数据时。需要额外的中间服务进行事务弥补。

  1. batch跑批 判断发起方是否需要补全processId或者删除数据。发起方预留一定时间给予审批服务处理时间(5-30min).中间服务获取在过了5-30min 后 processId缺失的数据,提取uniqueId,向审批服务验证uniqueId是否有效,有效则补全processId,无效则删除无效数据。

case 2:

场景:

审批链执行结束,审批成功,审批中心向发起方发起回调请求。因为审批操作不可逆,所以审批服务只能成功

1.审批完成-> 2.回调发起方,完成业务逻辑

实现原理

rabbitmq手动确认 + 死信队列实现两段尝试,发送消息 + 消费消息 统一根据uniqueId 落表,由batch跑批机制保证消息发送成功,消费成功

rabbitmq手动确认:

confirm:消息达到broker将触发回调,一旦失败记录日志,补充逻辑为重新发起消息

return :消息达到broker,broker根据exchange,routingKey发送消息时失败触发回调,一旦失败记录日志,补充逻辑为重新发起消息

死信队列:

发送消息补偿死信队列,根据预测业务处理时间设置ttl(5min),当5分钟后消息没有被消费者ack/nack,消息将进入死信队列进行消费  

操作:

日志表(以下称之为redoLog)

  • 记录业务是否执行成功done
  • 记录重试次数tryTime(默认值为1)
  • 记录下次被跑批扫到的时间activeTime
  • 记录本次执行结果/错误信息
  • 记录上报类型reportType (send/confirm/return/consume/dl/batch代表在何处记录的日志)
  • 记录重试操作的接口地址(重新发送消息/重新调用接口)url
  • 记录重新操作的接口参数,请求类型等

1.发送消息时记录日志

 查询日志表中有无记录(是不是第一次发送) ,无记录 insert,有记录 update

存在: 判断是否执行成功(done为Y/N)

                 成功:不处理,结束

                不成功:tryTime重试次数 + 1
           
           尝试次数超过5次(tryTime>5)发送邮件,将activeTime置为1小时后,代表需要1小时后,跑批才能扫到此数据。

            更新日志

不存在:记录日志  activeTime置为触发死信队列消费时间的两倍(10 min) 

2.confirm,return 触发

记录更新时间,activeTime置为当前时间 ,reportType置为confirm/return

3.消费

 查询日志表,判断是否已经执行成功(done为Y/N)成功:ack,结束

执行业务逻辑,如果成功,DONE置为Y,更新redoLog;如果失败,记录错误信息,nack,消息将进入死信再消费一次

4.死信消费:

 直接ack,消息不再进入任何队列

   判断是否执行成功(done为Y/N)成功: 结束

   否则tryTime+1 ,执行业务逻辑,如果成功,DONE置为Y,如果失败记录错误信息,将activeTime置为当前时间,更新日志

5.batch跑批

 查询 activeTime 小于等于当前时间的redoLog日志,且done 为N 的日志 回调日志中记录的url 接口(这里就是重新发送消息),如果失败  tryTime + 1,记录更新时间,reportType  ,同样5次后发邮件,修改activeTime时间为1小时后。

注意事项

需实现多次消费的幂等性问题,多次消费最终的结果应当保持一致。消费时需加分布式锁,防止重新发送的消息,和原始消息因网络原因 同时到达,导致同时消费。

此方案同时适用 层层消费 如 A-> B -> C -> ...前提是所有服务共用一个唯一标识,同时在层层回调时,
只需要关心自己的业务是否执行成功,在回调下一环时记录重新操作的接口参数/类型/地址 。
当然,这套方案不具备回滚性,要求实现的是必须成功。