补偿机制的实现

在日常开发中,经常需要跟对接第三方,一般场景下,失败了我们会直接把异常抛出,某些业务场景,失败了需要进行重试,而不是直接抛出异常,影响用户体验

1.使用Spring-Retry框架RetryTemplate重试,多次失败则记录,后续补偿

RetryTemplate是Spring自带的一个重试模板,支持模板的方式还有注解的方式,模板的方式比较灵活,暂使用该方式作为例子

  1. 引入maven依赖
 <dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
 </dependency>
  1. 直接上代码

以下是一个通过http请求调用第三方系统接口的例子

@Slf4j
public class Demo {

    private static final String SUCCESS = "S";
    private final String regex = "\\$(\\w+)\\$";
    private final OAConfig oaConfig;
    private final CompensationOperator compensationOperator;

    public void receiveTodoRequestByJson(String json) {
        /**
         * dev,sit,uat OA无对接环境,默认返回0
         */
        if (StrUtil.isBlank(oaConfig.getUrl())) {
            return;
        }

        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setRetryPolicy(new SimpleRetryPolicy());//默认重试3次
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        //fixedBackOffPolicy.setBackOffPeriod(1000 * 60 * 60);//重试时间间隔:1h
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        retryTemplate.execute(
                //普通业务实现,调用第三方系统接口,结果不符合则手动抛出异常
                context -> {
                    String response = HttpUtil.post(oaConfig.getUrl() + "/rest/ofs/ReceiveTodoRequestByJson", json);
                    Integer status = JSON.parseObject(response).getIntValue("operResult");
                    if (Objects.isNull(status) || (Objects.nonNull(status) && status != 1)) {
                        log.error("receiveTodoRequestByJson报错,重试次数:{},请求:{},响应:{}", context.getRetryCount(), json, response);
                        throw new BizException("500", "receiveTodoRequestByJson报错", "receiveTodoRequestByJson报错");
                    }
                    log.info("receiveTodoRequestByJson成功,重试次数:{},请求:{},响应:{}", context.getRetryCount(), json, response);
                    return status;
                },
                //多次失败后的兜底实现,记录当前的入参,类名,方法,后续通过补偿机制重试
                context -> {
                    Compensation compensation = new Compensation();
                    compensation.setJson(json);
                    compensation.setType(CompensationTypeEnum.OA_RECEIVE_TODO_REQUEST.getCode());
                    compensation.setClassName(this.getClass().getSimpleName());

                    StackTraceElement stackTraceElements = Thread.currentThread().getStackTrace()[1];

                    Pattern pattern = Pattern.compile(regex);
                    Matcher matcher = pattern.matcher(stackTraceElements.getMethodName());
                    if (matcher.find()) {
                        compensation.setMethodName(matcher.group(1));
                    } else {
                        compensation.setMethodName(stackTraceElements.getMethodName());
                    }
                    compensationOperator.save(compensation);
                    log.error("创建OA统一待办失败", context.getLastThrowable());
                    throw new BizException("", "", "创建OA统一待办失败");
                });
    }
}

2.补偿机制的实现

Compensation类


import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;

/**
 * 补偿表
 *
 * @TableName compensation
 */
@TableName(value = "compensation")
@Data
public class Compensation implements Serializable {
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    /**
     * 主键
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 是否删除,0否1是,完成后删除
     */
    @TableLogic
    private Integer isDelete;
    /**
     * 补偿类型
     */
    private Integer type;
    /**
     * json字符串,入参
     */
    private String json;
    /**
     * 全限定类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;

}

对应DDL

create table compensation
(
    id          bigint unsigned auto_increment comment '主键'
        primary key,
    type        int(2) unsigned         not null comment '补偿类型',
    json        text                    null comment 'json字符串,入参',
    class_name  varchar(255) default '' not null comment '全限定类名',
    method_name varchar(255) default '' not null comment '方法名',
    is_delete   tinyint(1)   default 0  not null comment '是否删除,0否1是,完成后删除'
)
    comment '补偿表';

补偿类型建议写一个枚举类来描述

image.png

3.通过xxl-job调用指定记录来重试实现补偿

可以xxl-job,可以直接搞个http接口,可以自动遍历表,可以手动指定重试,看自己喜欢
下面主要是通过获取上下文中的实例来重试,如果通过反射,newInstance的时候容易出问题


import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.dsl.promotion.svc.innerpart.entity.Compensation;
import com.dsl.promotion.svc.innerpart.operator.CompensationOperator;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.util.DateUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * @description
 * @date 2023/8/17 11:14
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CompensationTask {

    private final CompensationOperator compensationOperator;

    private final ApplicationContext applicationContext;

    /**
     * 人工补偿,传id
     */
    @XxlJob("manualCompensation")
    public void manualCompensation() {
        String param = XxlJobHelper.getJobParam();
        XxlJobHelper.log("人工补偿,入参:{},开始时间:{}", param, DateUtil.formatDateTime(new Date()));
        try {
            Long id = Long.parseLong(param);
            Compensation compensation = compensationOperator.getById(id);
            String className = compensation.getClassName();
            String methodName = compensation.getMethodName();

            Method method = Arrays.stream(SpringUtil.getBean(className).getClass().getMethods())
                    .filter(m -> m.getName().equals(methodName)).collect(Collectors.toList()).get(0);
            Object result = ReflectionUtils.invokeMethod(method, SpringUtil.getBean(className), compensation.getJson());
            compensationOperator.removeById(id);
            XxlJobHelper.log("人工补偿成功,结果:{},结束时间:{}", JSON.toJSONString(result), DateUtil.formatDateTime(new Date()));
        } catch (Exception e) {
            log.error("人工补偿失败", e);
            XxlJobHelper.log("人工补偿失败,异常原因:{}", e.getMessage());
            XxlJobHelper.handleFail();
            return;
        }
    }
}

调用job的时候,我们可以把id传进入就行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值