case 1
场景:
某服务向审批中心发起审批请求,要求实现该服务业务逻辑成功,同时成功发起审批。或者同时失败。同时审批服务一旦成功,将发起审批链,向相关人员推送消息,也就是当发起审批成功时,发起方即使发生奔溃,也要保证最终的数据一致性。
1.发起审批 -> 2.审批服务接收请求,响应结果 -> 3.提交/回滚
当发起方成功,审批服务失败,结果应当是发起失败,可重新发起审批。
如果发起方基于本地事务,根据审批接口结果,决定是否提交/回滚,无法应对crash场景。可能存在审批服务成功,本地事务来不及提交就down掉的场景。
实现原理
互相传递唯一编号标识,依赖跑批进行数据纠正
操作:
-
发起方只要保证发起审批前的原子性即可,业务逻辑执行成功,发送 唯一编号 uniqueId至审批服务。
-
审批服务保存 uniqueId, 一旦成功,回传 唯一编号 processId
-
发起方调用审批接口返回后,更新processId至相关表中。如果返回的结果为fail,删除业务表中相关数据
当回传success,本地crash 没来的及保存processId,或者回传fail,没来得及删除业务数据时。需要额外的中间服务进行事务弥补。
- 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 -> ...前提是所有服务共用一个唯一标识,同时在层层回调时,
只需要关心自己的业务是否执行成功,在回调下一环时记录重新操作的接口参数/类型/地址 。
当然,这套方案不具备回滚性,要求实现的是必须成功。