递增重试(二)

简易版的递增重试请看递增重试

背景:

这篇文章更符号实际项目中的需求,比如用户发起一笔支付,我们后台会去调银行的支付接口,并提供给银行一份支付回调地址。一般来说支付接口调用成功之后,过个几秒钟,银行会回调我们提供的接口,我们在这个接口里根据银行的信息,更新我们自己的订单状态。但有时候银行由于某些原因不会主动或者过了很久才会回调我们,如果我们不做处理的话,订单状态将会持续很久的 支付中。那一般这个时候我们去起一个定时任务,去查询银行的支付结果接口,然后更新我们的订单状态,这里就涉及到 递增重试。对于每笔订单来说,第一次每隔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);
        }


    }
}
发布了165 篇原创文章 · 获赞 103 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/qq_33101675/article/details/104679368