自己实现公共重试组件

公共重试组件

一个公共的重试组件,可以用于“接口重试”、“job重试”

公共重试任务枚举类

公共重试任务枚举类,这个枚举类可自定义,方便和异步数据进行交互

/**
 * <p>
 * 公共重试任务枚举类
 * </p>
 *
 * @author meng
 * @since 2023/5/11
 */
public enum CommonRetryJopEnum {

    PROMOTER_ENABLE_ACCOUNT("000000", "/v1/api/paperExpress/save", 5, RequestModeEnum.POST, "code", String.valueOf(R.success().getCode()), "促销员启用账号权限"),
    PROMOTER_DISABLE_ACCOUNT("000001", "/v1/api/paperExpress/save", 5, RequestModeEnum.POST, "code", String.valueOf(R.success().getCode()), "促销员禁用账号权限"),
    ;


    private String taskId; //任务标识
    private String url; //任务标识(需要外面拼接成域名)
    private int retryCount; //计划重试次数
    private RequestModeEnum requestMode; //方法名称
    private String successKey; //成功key
    private String successCode; //成功返回码
    private String desc; //说明

    public static CommonRetryJopEnum getInstance(String taskId) {
        for (CommonRetryJopEnum retryJopEnum : CommonRetryJopEnum.values()) {
            if (retryJopEnum.getTaskId().equals(taskId)) {
                return retryJopEnum;
            }
        }
        return null;
    }

    CommonRetryJopEnum(String taskId, String url, int retryCount, RequestModeEnum requestMode, String successKey, String successCode, String desc) {
        this.taskId = taskId;
        this.url = url;
        this.retryCount = retryCount;
        this.requestMode = requestMode;
        this.successKey = successKey;
        this.successCode = successCode;
        this.desc = desc;

    }

    public String getTaskId() {
        return taskId;
    }

    public String getUrl() {
        return url;
    }

    public int getRetryCount() {
        return retryCount;
    }

    public RequestModeEnum getRequestMode() {
        return requestMode;
    }

    public String getSuccessKey() {
        return successKey;
    }

    public String getSuccessCode() {
        return successCode;
    }

    public String getDesc() {
        return desc;
    }
}

重试任务

重试任务


/**
 * <p>
 * 公共http重试公共任务
 * </p>
 *
 * @author meng
 * @since 2023/5/11
 */
@Component
@Slf4j
@Configuration
@EnableScheduling
public class CommonRetryJob {

    @Autowired
    private CommonRetryJobService retryJobService;

    /**
     * 5分钟一次,5秒后开始执行
     */
    @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 5000)
    public void execute() {
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start("公共重试任务");
            ToolUtil.setTenantCode(ToolUtil.getTaskTenantcode());
            retryJobService.execute();
            stopWatch.stop();
            log.info("{},耗时:{}", stopWatch.getLastTaskName(), stopWatch.getTotalTimeSeconds());
        } catch (Exception e) {
            log.error("公共重试任务异常,错误信息为:{}", e);
        }
    }

}

业务类中,如果需要重试的,通过在catch中调用addRetryJob,将任务放到重试任务表中,待任务异步执行

/**
 * <p>
 * http公共重试表
 * </p>
 *
 * @author meng
 * @since 2023/5/11
 */
public interface CommonRetryJobService {

    boolean addRetryJob(CommonRetryJopEnum commonRetryJopEnum, String url, String parameter, String response);

    void execute();

}


/**
 * <p>
 * CommonRetryJobServiceImpl
 * </p>
 *
 * @author meng
 * @since 2023/5/11
 */
@Service
@Slf4j
public class CommonRetryJobServiceImpl implements CommonRetryJobService {

    @Autowired
    private CommonRetryJobMapper commonRetryJobMapper;
    @Autowired
    private CommonRetryOverExecuteFactory commonRetryOverExecuteFactory;

    private int processedSize = 4;
    private int intervalMinute = 5;

    @Resource
    private RedisDistributeLock redisDistributeLock;

    private BlockingQueue<CommonRetryJobDO> queue = new LinkedBlockingDeque<>(40);
    ExecutorService executorService = Executors.newFixedThreadPool(4);
    // private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 10, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(30),
    //         new ThreadFactory() {
    //             @Override
    //             public Thread newThread(Runnable r) {
    //                 return new Thread(r, "[公共接口重试线程]");
    //             }
    //         },
    //         new RejectedExecutionHandler() {
    //             @Override
    //             public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    //                 log.error("this Thread Name rejected task!");
    //             }
    //         });


    @Override
    public boolean addRetryJob(CommonRetryJopEnum commonRetryJopEnum, String url, String parameter, String response) {
        CheckUtils.isTrue(commonRetryJopEnum == null || HttpUtils.isNotUrl(url) || StringUtils.isBlank(parameter), BizError.COMMON_RETRY_ADD_PARAMETER_EXCEPTION);

        CommonRetryJobDO retryJobDO = new CommonRetryJobDO();
        retryJobDO.setTaskId(commonRetryJopEnum.getTaskId());
        retryJobDO.setStatus(ComTaskStatusEnum.PROCESSED.getValue());
        retryJobDO.setRetryCount(commonRetryJopEnum.getRetryCount());
        retryJobDO.setHasRetryCount(0);
        retryJobDO.setUrl(url);
        retryJobDO.setRequestMode(commonRetryJopEnum.getRequestMode().getKey());
        retryJobDO.setParameter(parameter);
        retryJobDO.setResponse(response);
        retryJobDO.setSuccessKey(commonRetryJopEnum.getSuccessKey());
        retryJobDO.setSuccessCode(commonRetryJopEnum.getSuccessCode());
        return commonRetryJobMapper.save(retryJobDO);
    }

    @Override
    public void execute() {
        redisDistributeLock.lockNoWait(RedisEnum.PROMOTER_ENABLE_ACCOUNT_RETRY_KEY.key, null, x -> {
            //1、查询待处理任务
            List<CommonRetryJobDO> dbJobList = commonRetryJobMapper.queryByStatus(ComTaskStatusEnum.PROCESSED.getValue(), processedSize, intervalMinute - 1);
            if (CollectionUtils.isEmpty(dbJobList)) {
                return R.success();
            }
            //2、加入任务队列中
            for (CommonRetryJobDO dbJobDO : dbJobList) {
                queue.add(dbJobDO);
            }
            //3、修改任务状态为'执行中'
            List<Long> ids = dbJobList.stream().map(CommonRetryJobDO::getId).collect(Collectors.toList());
            commonRetryJobMapper.batchUpdateStatus(ComTaskStatusEnum.PROCESSING.getValue(), ids);
            return R.success();
        });
    }

    @PostConstruct
    private void doJobTask() {
        log.info("跑批任务开始");
        ToolUtil.setTenantCode(ToolUtil.getTaskTenantcode());
        executorService.submit(() -> {
            try {
                while (true) {
                    CommonRetryJobDO reqDO = queue.take();
                    doRequestAndUpdateStatus(reqDO);
                }
            } catch (Exception e) {
                log.error("添加跑批任务失败", e);
            }
        });
    }

    /**
     * 重试接口 & 修改状态
     */
    private void doRequestAndUpdateStatus(CommonRetryJobDO retryJobDO) {
        //1、发起请求
        Map<String, Object> map = httpRestTemplate(RequestModeEnum.getInstance(retryJobDO.getRequestMode()), retryJobDO.getUrl(), retryJobDO.getParameter());
        //2、根据得到的结果修改成功状态,{5}次后预警人工处理
        updateStatus(map, retryJobDO);
    }

    /**
     * 接口重试
     */
    private Map<String, Object> httpRestTemplate(RequestModeEnum requestMode, String url, String parameter) {
        Map<String, Object> map = null;
        try {
            JSONObject jsonObject = JSON.parseObject(parameter);
            if (RequestModeEnum.POST.equals(requestMode)) {
                map = HttpUtils.request(HttpMethod.POST, jsonObject, url);
            } else if (RequestModeEnum.GET.equals(requestMode)) {
                map = HttpUtils.request(HttpMethod.GET, jsonObject, url);
            } else {
                log.error("没有该请求方式,请自行补充");
            }
            return map;
        } catch (Exception e) {
            log.error("重试调用异常:{}", e);
        }
        return map;
    }

    /**
     * 更新状态
     */
    private void updateStatus(Map<String, Object> map, CommonRetryJobDO dbJobDO) {
        CommonRetryJobDO retryJobDO = new CommonRetryJobDO();
        retryJobDO.setId(dbJobDO.getId());
        //1、如果不为空且等于成功的值则修改状态为成功
        if (null != map && StringUtils.equals(dbJobDO.getSuccessCode(), String.valueOf(map.get(dbJobDO.getSuccessKey())))) {
            //返回不为空并且返回成功则修改状态为成功
            retryJobDO.setStatus(ComTaskStatusEnum.DEAL_SUCCESS.getValue());
            try {
                //重试成功,回调操作
                CommonRetryOverExecuteService bean = commonRetryOverExecuteFactory.getBean(dbJobDO.getTaskId());
                bean.execute(dbJobDO);
            } catch (Exception e) {
                // 预警人工干预
                log.error("接口重试成功,但是业务处理未完成!");
            }
        } else {
            //2.1、如果失败已经达到重试次数则修改状态为失败,并且预警
            if (dbJobDO.getHasRetryCount() >= dbJobDO.getRetryCount() - 1) {
                log.error("重试{}任务已达重试上线:{}次,现已停止重试,请联系人工处理", CommonRetryJopEnum.getInstance(dbJobDO.getTaskId()).getDesc(), dbJobDO.getRetryCount());
                retryJobDO.setStatus(ComTaskStatusEnum.DEAL_FAILURE.getValue());
            } else {
                //2.2、如果失败,并且没带到成功上线则修改状态为待处理,进入下一次定时任务
                retryJobDO.setStatus(ComTaskStatusEnum.PROCESSED.getValue());
            }
        }
        retryJobDO.setHasRetryCount(dbJobDO.getHasRetryCount() + 1);
        retryJobDO.setResponse(JSONObject.toJSONString(map));
        commonRetryJobMapper.updateStatus(retryJobDO);
    }

}

重试失败/成功回调

回调工厂


/**
 * <p>
 * 公共重试组件回调任务创建工厂
 * </p>
 *
 * @author
 * @since 2023/5/12
 */
@Component
@Slf4j
public class CommonRetryOverExecuteFactory implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {

    public static ApplicationContext applicationContext;
    // 防止被多个spring容器执行
    private static volatile boolean isInit = false;
    private static Map<CommonRetryJopEnum, Class<? extends CommonRetryOverExecuteService>> map = new ConcurrentHashMap<>(4);
    private static Map<CommonRetryJopEnum, CommonRetryOverExecuteService> classMap = new ConcurrentHashMap<>(4);

    //需要执行的子类方法
    static {
        map.put(CommonRetryJopEnum.PROMOTER_ENABLE_ACCOUNT, PromoterYhdosAccountServiceImpl.class);
        map.put(CommonRetryJopEnum.PROMOTER_DISABLE_ACCOUNT, PromoterYhdosAccountServiceImpl.class);

    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        if (this.applicationContext == null) {
            this.applicationContext = context;
        }
    }

    /**
     * 通过taskId获取对应的子类对象
     */
    public static CommonRetryOverExecuteService getBean(String code) {
        CommonRetryJopEnum classEnum = CommonRetryJopEnum.getInstance(code);
        return classMap.get(classEnum);
    }

    /**
     * spring容器初始化完成后执行
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        initClassMap();
    }

    private void initClassMap() {
        //防止被多个spring容器执行
        if (isInit) {
            log.info("initClassMap has init");
            return;
        }
        for (CommonRetryJopEnum classEnum : map.keySet()) {
            Class<? extends CommonRetryOverExecuteService> clazz = map.get(classEnum);
            CommonRetryOverExecuteService executeService = applicationContext.getBean(clazz);
            classMap.put(classEnum, executeService);
        }
        //初始化完成后将标识改为true
        isInit = true;
    }

}

业务类实现CommonRetryOverExecuteService,进行回调


/**
 * <p>
 * PromoterYhdosAccountService
 * </p>
 *
 * @author meng
 * @since 2023/5/12
 */
public interface PromoterYhdosAccountService extends CommonRetryOverExecuteService {
    /**
     * 业务方法
     */
    ResEnablePromoterYhdosAccount enablePromoterYhdosAccount(@RequestBody ReqEnablePromoterYhdosAccount o);
}



/**
 * <p>
 * PromoterYhdosAccountServiceimpl
 * </p>
 *
 * @author
 * @since 2023/5/12
 */
@Service
@Slf4j
public class PromoterYhdosAccountServiceImpl implements PromoterYhdosAccountService {

    @Autowired
    private CommonRetryJobService commonRetryJobService;

    @Autowired
    private PromoterYhdosAccountClient promoterYhdosAccountClient;

    /**
     * 业务方法 (这是一段伪代码)
     */
    @Override
    public ResEnablePromoterYhdosAccount enablePromoterYhdosAccount(ReqEnablePromoterYhdosAccount o) {
       try {
            // 调用接口
            R<ResEnablePromoterYhdosAccount> resEnablePromoterYhdosAccountR = promoterYhdosAccountClient.enablePromoterYhdosAccount(null);
            //正常结束回调
            this.execute();
        }catch (Exception e) {
            commonRetryJobService.addRetryJob(null);
        }
        return null;
    }


    /**
     * 实现:接口重试完成公共回调
     */
    @Override
    public void execute(CommonRetryJobDO retryJobDO) {

    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值