业务需求
说明:
接收支付结果消息
付款是批量付款,一个付款对应多个付款详情,需要对多个供应商付款。
因为同时付款所以支付结果 mq 基本也是同时接收到。
业务诉求,当支付详情都收到支付结果后,支付单状态变为支付完成。
亮点:
处理方案,以付款单 id为纬度进行加锁。
不加锁后果:有 A、B 两个支付 详情,线程1收到支付成功消息,更新 A 为支付成功,查询是否所有都已支付成功,这时 B 尚未支付成功,所以不更新付款单状态为支付完成;在 A 线程提交事务前,线程2收到 B 支付成功消息,更新 B 为支付成功,然后查询是否都已支付,因为此时 A 线程尚未提交,所以查询的是 A 线程还未支付,不更新付款单状态。最终造成付款单状态不能够变为支付完成。
发起支付时:--初始方案
1.调用远程转账接口发起支付,发起失败,整体事务不回滚,会记录当前发起支付失败状态;调用支付接口超时同时也是记录发起支付失败状态。
2.转账接口会根据传的全局唯一号做幂等,支持重复发起。每个支付详情与一个全局唯一号绑定。
3.会有定时任务定时扫描,发起失败的数据,重新调用转账接口进行支付。
4.为避免同时发起支付,发起支付根据付款单 id 进行加锁。
5.先保存支付详情和转账全局唯一幂等号的关联关系,并设置状态为转账发起中;再调用转账接口,再更新支付详情状态为发起成功或者失败。原因:发起状态在调用接口后才知道,如果调用接口成功了,但是保存本地失败了,再转账会生成新的转账幂等号,会重复转账。
6.为什么不适用付款详情的编号作为转账的幂等号?因为转账失败的话,可以再此付款详情中再发起转账,如果使用付款详情的编号,再次发起,转账接口端判断之前处理过了不会再处理。
说明:上面操作在一个事务中,存在大事务问题,如果在本地事务中调用远程服务,那样本地该事务的操作就与提供远程服务的接口强依赖了,如果远程服务出现了问题,那么就会拖长事务,事务长时间没有提交,数据库连接就不会被释放,随着太多的数据库连接被占用,可能会导致数据库崩溃。
发起支付:--优化后方案
1.查询付款详情是否已经生成转账全局编号,无的话则生成,然后保存全局编号和付款详情的关联关系,更新时使用数据库乐观锁方式,mq 发送状态初始化为发送中,转账状态初始化为发起中,使用数据库默认事务,数据落库。
2.状态变为付款发起中,发送mq 消息,根据 mq 发送成功与否更新 mq 发送状态。
3.发送 mq 消息失败,进行告警,人工看原因。此时不回滚,页面可以重新发起。会定时扫描一直发起中付款记录中发送失败或者一直发送中的数据,重新发送 mq。
4.整体无事务。
消费方:
- 以付款全局号+支付详情单号作为全局幂等号加分布式锁
- 以付款全局号+支付详情单号作为全局幂等号,查询去重表,看是否已经处理。
- 接收 mq 消息,根据付款详情编号查到付款详情记录,付款发起中状态则调用远程付款接口。
- 如果调用远程付款接口超时,mq 消息处理失败,进入重试。
- 如果调用远程接口返回失败或成功,更新付款详情表状态为发起失败,页面可再发起,同样乐观锁方式更新。
- 插入去重表
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。