简易版的递增重试请看递增重试
背景:
这篇文章更符号实际项目中的需求,比如用户发起一笔支付,我们后台会去调银行的支付接口,并提供给银行一份支付回调地址。一般来说支付接口调用成功之后,过个几秒钟,银行会回调我们提供的接口,我们在这个接口里根据银行的信息,更新我们自己的订单状态。但有时候银行由于某些原因不会主动或者过了很久才会回调我们,如果我们不做处理的话,订单状态将会持续很久的 支付中。那一般这个时候我们去起一个定时任务,去查询银行的支付结果接口,然后更新我们的订单状态,这里就涉及到 递增重试。对于每笔订单来说,第一次每隔5分钟查询一次,第二次也是5分钟,依然没有结果的话,第三次10分钟...以此类推
方法:
1、我们有一张订单表,记录订单信息
2、我们有一张补偿任务表,关联订单ID,并记录该订单的重试次数,下一次的重试时间,任务的状态等
3、在我们调银行支付接口成功的时候,会新增数据到 补偿任务表
4、如果银行回调过来,我们 更新订单表和补偿任务表的状态
5、如果银行不回调,我们起定时任务查询 补偿任务表
撸码:
TaskEnum 任务状态枚举,可以自行扩展
public enum TaskEnum {
INIT("I", "初始态"),
RETRY("R", "重试"),
ABORT("A", "超过最大重试次数"),
SUCCESS("S", "成功"),
FAILURE("F", "失败"),
;
private String code;
private String desc;
TaskEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
TaskInfo 补偿任务表
@Data
public class TaskInfo implements Serializable {
private static final long serialVersionUID = 1L;
private Long orderId;
private String status = TaskEnum.INIT.getCode();
private List<String> statusList;
/**
* 重试次数
*/
private int retryNo;
/**
* 下次重试时间
*/
private Date nextRetryTime;
/**
* 增加重试次数
*/
public void increaseRetryNo() {
int oldRetryNo = Integer.valueOf(this.retryNo);
this.retryNo = (Integer.MAX_VALUE > oldRetryNo) ? (oldRetryNo + 1) : 0;
}
}
RetryStrategy 重试策略
@Component
public class RetryStrategy {
/**
* 重试间隔(s)
*/
private long[] retryIntervals = new long[]{5 * 60, 5 * 60, 10 * 60, 10 * 60, 30 * 60, 30 * 60, 60 * 60, 60 * 60};
/**
* 计算下一次的重试时间
*
* @param retryNo
* @return
*/
public Date nextRetryTime(int retryNo) {
if (retryNo >= retryIntervals.length) {
return null;
}
return new Date(new Date().getTime() + retryIntervals[retryNo] * 1000L);
}
}
QueryOrderResultTask 定时任务(里面包含 支付与回调的 伪代码)
/**
* 调银行接口 查询支付的结果
* 1、一般银行会把结果回调过来,然后我们更新订单表的状态
* 2、如果过了很久都没有回调的话,我们起个定时任务去调银行接口查询订单的支付结果
* 3、对于每笔订单来说,第一次每隔5分钟查询一次,第二次也是5分钟,依然没有结果的话,第三次10分钟...
* 3、这里是针对第2种情况,设计补偿任务的重试策略
*/
@Component
public class QueryOrderResultTask {
@Resource
RetryStrategy retryStrategy;
/**
* 调银行接口支付 伪代码
*
* @return
*/
public String pay() {
// 1、调银行接口支付
String result = callBankPayInterface();
// 2、成功之后,新增 TaskInfo 表
if ("成功".equals(result)) {
TaskInfo taskInfo = new TaskInfo();
taskInfo.setStatus(TaskEnum.INIT.getCode());
taskInfo.setRetryNo(0);
taskInfo.setNextRetryTime(retryStrategy.nextRetryTime(taskInfo.getRetryNo()));
dao.saveTaskInfo(taskInfo);
}
return "OK";
}
/**
* 银行回调接口 伪代码
*
* @return
*/
public String callback(Map<String, String> map) {
String code = map.get("code");
if("支付成功".equals(code)){
// 1、更新订单状态为 支付成功
// 2、更新 补偿任务表的状态为 成功
TaskInfo taskInfo = new TaskInfo();
taskInfo.setStatus(TaskEnum.SUCCESS.getCode());
dao.updateTaskInfo(taskInfo);
}
return "OK";
}
/**
* 定时任务
*/
@Scheduled(cron = "0 5 0 * * ?")
public void execute() {
//1、查询 TaskInfo 表,状态为初始态和重试态的,且下次重试时间 <= new Date()
TaskInfo taskInfo = new TaskInfo();
taskInfo.setStatusList(new ArrayList<String>(Arrays.asList(TaskEnum.INIT.getCode(), TaskEnum.RETRY.getCode())));
//nextRetryTime <= new Date()
List<TaskInfo> resultList = dao.queryTaskInfo(taskInfo);
for (TaskInfo info : resultList) {
//调银行查询支付结果接口
try {
String result = callBankQueryResultInterface();
}catch (Exception e){
}
// 根据自己的业务情况,确定成功给成功,确定失败给失败(即重试再多次也还是失败),不确定的需要重试
if ("支付成功".equals(result)) {
info.setStatus(TaskEnum.SUCCESS.getCode());
} else if ("支付失败".equals(result)) {
info.setStatus(TaskEnum.SUCCESS.getCode());// 任务状态给成功,不需要再重试
} else if ("暂时还没有结果".equals(result)) {
Date nextRetryTime = retryStrategy.nextRetryTime(info.getRetryNo());
if(null == nextRetryTime){
System.out.println("已经超过最大重试次数");
info.setStatus(TaskEnum.ABORT.getCode());
}else{
System.out.println("还未重试至最大次数");
info.setStatus(TaskEnum.RETRY.getCode());
info.increaseRetryNo();
info.setRetryNo(info.getRetryNo());
info.setNextRetryTime(nextRetryTime);
}
}
//更新 taskInfo 表
dao.updateTaskInfo(taskInfo);
}
}
}