文章目录
- 1 问题背景
- 2 前言
- 3 解决方案
- 3.1 核心思路
- 3.2 数据库设计
- 3.3 下一次发送webhook的时间算法
- 3.3 详细设计
- 4 延申思考
1 问题背景
作为一个收单系统,当获取到一笔交易的支付结果时,就需要发送一个webhook消息给电商系统。电商系统收到webhook消息后,会翻转支付状态。如果发送webhook失败,那么电商系统无法拿到webhook消息从而翻转支付状态,那么就会有问题。对于买家而言则是明明已经支付了,在电商系统这里却未支付,买家可能会要求卖家退款或者取消订单。对于卖家而言则是明明收到款了,但是电商系统那里却显示为未支付,这样减少了支付转化率以及降低了买家的顾客体验。
2 前言
本博客讲述的是生产环境真实遇到的场景,不是那种八股文或者小打小闹的demo。
3 解决方案
3.1 核心思路
采用XXL-JOB定时调度+最大重试次数+重试频率递减的重试机制
3.2 数据库设计
支付单的表需要加入3个字段:
is_webhook
:是否已经成功发送webhookretry_times
:已重试的次数next_retry_webhook_time
:下一次发送webhook的时间
3.3 下一次发送webhook的时间算法
考虑到如果第1次发送失败,那么紧接着立刻发送重试的成功率不会太高,因此设计一个重试频率递减的算法。
下一次发送 webhook 的时间 = (已重试的次数+1) * 5min + 当前时间
3.3 详细设计
- 使用XXL-JOB定时调度,每6min调度一次。调度间隔可以根据实际情况调整,不一定是6min
- 最大重试次数可以采用apollo配置动态获取,由于改最大次数也不会经常改变,也可以采用硬编码实现。
下一次重新发送webhook的时间 = (已重试的次数+1) * 5min + 当前时间
,实现频率递减的效果。 - 查询待重试发送webhook的支付单。查询条件必须是未发送webhook成功,且已重试次数小于最大重试次数,且下一次发送webhook的时间要小于当前时间。由于要用到下一次发送webhook的时间,因此需要给该字段加索引
- 如果重试次数用完了,可以手动重置重试次数
- 不论发送成功还是发送失败,都要打上关键日志
4 延申思考
本文讲述的解决方案与Nacos的健康检查有异曲同工之处。
Nacos同样是用异步线程执行定时任务,每隔5秒就会扫描注册上来的实例。Nacos会判断实例上次发送心跳的时间,到当前时间差了多少,如果超过15s则标记该实例,再过15s仍然没有发来心跳,Nacos则剔除该实例。
本文解决方案同样采用定时任务,每隔6min扫描需要重试发送webhook的支付单。当未成功发送webhook,且重试次数未达到最大,且下一次发送webhook时间到达了,则会开始重试发送webhook。同时本文采用了一个重试频率递减的重试机制。