公共重试组件
一个公共的重试组件,可以用于“接口重试”、“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) {
}
}