一、概述
微服务之间相互调用,难免会出现形形色色的异常,出现异常时有些情况可能需要先落重试任务表,然后通过任务调度等进行定时重试;通过自定义重试注解@Retryable,减少对核心业务代码入侵,增强代码可读性、可维护性。下面通过实战,开发自定义重试注解@Retryable。诸位可根据业务需要,稍作改造直接使用;如果有疑问、或者好的想法,欢迎留言,经验共享。
二、实战
重试任务表定义(retry_task):
CREATE TABLE `retry_task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键值',
`business_type_code` varchar(32) COLLATE NOT NULL DEFAULT '' COMMENT '业务类型编码',
`business_type_desc` varchar(100) COLLATE NOT NULL DEFAULT '' COMMENT '业务类型描述',
`retry_service_name` varchar(100) COLLATE NOT NULL DEFAULT '' COMMENT '重试的service名称',
`business_param` text COLLATE NOT NULL DEFAULT '' COMMENT '业务参数',
`wait_retry_times` int(11) NOT NULL DEFAULT 3 COMMENT '待重试次数',
`already_retry_times` int(11) NOT NULL DEFAULT 0 COMMENT '已重试次数',
`retry_result_code` varchar(36) COLLATE NOT NULL DEFAULT '' COMMENT '重试结果码',
`retry_result_msg` varchar(255) COLLATE NOT NULL DEFAULT '' COMMENT '重试结果描述',
`create_user` varchar(36) COLLATE NOT NULL DEFAULT '' COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_user` varchar(36) COLLATE NOT NULL DEFAULT '' COMMENT '更新人',
`update_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_business_type_code` (`business_type_code`)
) COMMENT='重试任务表';
重试任务表实体类(RetryTaskEntity):
@Data
public class RetryTaskEntity implements Serializable {
private static final long serialVersionUID = -1950778520234119369L;
/**
* 主键值
*/
private BigInteger id;
/**
* 业务类型编码
*/
private String businessTypeCode;
/**
* 业务类型描述
*/
private String businessTypeDesc;
/**
* 重试的service名称
*/
private String retryServiceName;
/**
* 业务参数
*/
private String businessParam;
/**
* 待重试的次数
*/
private Integer waitRetryTimes;
/**
* 已重试的次数
*/
private Integer alreadyRetryTimes;
/**
* 重试结果码
*/
private String retryResultCode;
/**
* 重试结果描述
*/
private String retryResultMsg;
/**
* 创建人
*/
private String createUser;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private String updateUser;
/**
* 更新时间
*/
private Date updateTime;
}
重试任务表mapper和对应的xml文件:
public interface RetryTaskMapper {
int addRetryTask(RetryTaskEntity retryTaskEntity);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.boot.demo.mapper.RetryTaskMapper">
<insert id="addRetryTask" parameterType="com.boot.demo.pojo.RetryTaskEntity">
INSERT INTO retry_task(business_type_code,
business_type_desc,
retry_service_name,
business_param,
wait_retry_times,
already_retry_times,
retry_result_code,
retry_result_msg,
create_user,
create_time,
update_user,
update_time)
VALUES (#{businessTypeCode},
#{businessTypeDesc},
#{retryServiceName},
#{businessParam},
#{waitRetryTimes},
#{alreadyRetryTimes},
#{retryResultCode},
#{retryResultMsg},
#{createUser},
#{createTime},
#{updateUser},
#{updateTime})
</insert>
</mapper>
重试任务表service和对应的serviceImpl:
public interface RetryTaskService {
void addRetryTask(RetryTaskEntity retryTaskEntity);
}
@Service
public class RetryTaskServiceImpl implements RetryTaskService {
@Autowired
private RetryTaskMapper retryTaskMapper;
@Override
public void addRetryTask(RetryTaskEntity retryTaskEntity) {
retryTaskMapper.addRetryTask(retryTaskEntity);
}
}
业务类型枚举类(RetryTaskDefinitionEnum):
/**
* 重试任务枚举
*/
public enum RetryTaskDefinitionEnum {
ADD_STOCK("101", "采购入库成功后新增库存异常重试", "purchaseService", 3);
/**
* 业务类型编码
*/
private final String businessTypeCode;
/**
* 业务类型描述
*/
private final String businessTypeDesc;
/**
* 重试的service名称
*/
private final String retryServiceName;
/**
* 重试次数
*/
private final Integer retryTimes;
RetryTaskDefinitionEnum(String businessTypeCode, String businessTypeDesc, String retryServiceName, Integer retryTimes) {
this.businessTypeCode = businessTypeCode;
this.businessTypeDesc = businessTypeDesc;
this.retryServiceName = retryServiceName;
this.retryTimes = retryTimes;
}
public static RetryTaskDefinitionEnum getTaskDefinitionByBusinessTypeCode(String businessTypeCode) {
if (StringUtils.isBlank(businessTypeCode)) {
return null;
}
for (RetryTaskDefinitionEnum taskDefinition : values()) {
if (taskDefinition.getBusinessTypeCode().equals(businessTypeCode)) {
return taskDefinition;
}
}
return null;
}
public String getBusinessTypeCode() {
return businessTypeCode;
}
public String getBusinessTypeDesc() {
return businessTypeDesc;
}
public String getRetryServiceName() {
return retryServiceName;
}
public Integer getRetryTimes() {
return retryTimes;
}
}
自定义注解(@MyRetryable):
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface MyRetryable {
RetryTaskDefinitionEnum businessType();
}
自定义注解切面(MyRetryableAspect):
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import com.boot.demo.result.Result;
import com.boot.demo.result.ResultCode;
import com.boot.demo.pojo.RetryTaskEntity;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import com.boot.demo.annotation.MyRetryable;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.ProceedingJoinPoint;
import com.boot.demo.service.RetryTaskService;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.boot.demo.annotation.RetryTaskDefinitionEnum;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
@Slf4j
@Aspect
@Component
public class MyRetryableAspect {
@Autowired
private RetryTaskService retryTaskService;
@Pointcut("@annotation(com.boot.demo.annotation.MyRetryable)")
public void pointCut() {
}
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
Result result = null;
try {
// 执行目标方法
result = (Result) joinPoint.proceed();
// 目标方法返回:成功结果码(200),则无需重试
if (ResultCode.SUCCESS.getCode() == result.getCode()) {
return result;
}
// 目标方法返回:非成功结果码(非200)则需重试(此次可根据需要判断什么样的返回码需要重试)
dealAddRetryTask(joinPoint);,
return result;
} catch (Throwable e) {
log.error("myRetryableAspectLog error param: {} result: {} e: ", joinPoint.getArgs(), result, e);
// 此处捕获异常之后,也可以根据需要重试,这里就仅输出异常日志
return result;
}
}
private void dealAddRetryTask(ProceedingJoinPoint joinPoint) {
// 获取重试注解信息
MyRetryable myRetryableAnnotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MyRetryable.class);
if (null == myRetryableAnnotation) {
return;
}
// 根据业务类型编码,获取枚举中定义的业务类型描述、重试的service、重试次数等信息
String businessTypeCode = myRetryableAnnotation.businessType().getBusinessTypeCode();
RetryTaskDefinitionEnum retryTaskDefinition = RetryTaskDefinitionEnum.getTaskDefinitionByBusinessTypeCode(businessTypeCode);
if (null == retryTaskDefinition) {
return;
}
RetryTaskEntity retryTaskEntity = new RetryTaskEntity();
retryTaskEntity.setBusinessTypeCode(businessTypeCode);
retryTaskEntity.setBusinessTypeDesc(retryTaskDefinition.getBusinessTypeDesc());
retryTaskEntity.setRetryServiceName(retryTaskDefinition.getRetryServiceName());
retryTaskEntity.setBusinessParam(JSON.toJSONString(joinPoint.getArgs()[0]));
retryTaskEntity.setWaitRetryTimes(retryTaskDefinition.getRetryTimes());
retryTaskEntity.setAlreadyRetryTimes(0);
retryTaskEntity.setRetryResultCode("");
retryTaskEntity.setRetryResultMsg("");
retryTaskEntity.setCreateUser("SYS");
retryTaskEntity.setCreateTime(new Date());
retryTaskEntity.setUpdateUser("SYS");
retryTaskEntity.setUpdateTime(new Date());
retryTaskService.addRetryTask(retryTaskEntity);
}
}
基础类(Result、ResultCode、ResultGenerator)。
Result类:
public class Result {
private int code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.getCode();
return this;
}
public int getCode() {
return code;
}
public Result setCode(int code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public Result setMessage(String message) {
this.message = message;
return this;
}
public Object getData() {
return data;
}
public Result setData(Object data) {
this.data = data;
return this;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Result{");
sb.append("code=").append(code);
sb.append(", message='").append(message).append('\'');
sb.append(", data=").append(data);
sb.append('}');
return sb.toString();
}
}
ResultCode类:
public enum ResultCode {
SUCCESS(200),
FAIL(400),
UNAUTHORIZED(401),
FORBIDDEN(403),
NOT_FOUND(404),
INTERNAL_SERVER_ERROR(500);
private final int code;
ResultCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
ResultGenerator类:
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
private ResultGenerator() {
}
public static Result genSuccessResult() {
return new Result().setCode(ResultCode.SUCCESS).setMessage(DEFAULT_SUCCESS_MESSAGE);
}
public static Result genSuccessResult(Object data) {
return new Result().setCode(ResultCode.SUCCESS).setMessage(DEFAULT_SUCCESS_MESSAGE).setData(data);
}
public static Result genFailResult(String message) {
return new Result().setCode(ResultCode.FAIL).setMessage(message);
}
public static Result genFailResult(ResultCode code, String message) {
return new Result().setCode(code).setMessage(message);
}
public static Result genFailResult(String message, Object data) {
return new Result().setCode(ResultCode.FAIL).setMessage(message).setData(data);
}
}
测试controller(PurchaseController):
@RestController
@RequestMapping("/purchase")
public class PurchaseController {
@Autowired
private PurchaseService purchaseService;
@GetMapping("/test")
public String test(String param) {
purchaseService.addStock(param);
return "success";
}
}
测试PurchaseService、和PurchaseServiceImpl
public interface PurchaseService {
Result addStock(String param);
}
@Service("purchaseService")
public class PurchaseServiceImpl implements PurchaseService {
@Override
// 在需要重试的业务方法上新增重试注解即可
@MyRetryable(businessType = RetryTaskDefinitionEnum.ADD_STOCK)
public Result addStock(String param) {
// return ResultGenerator.genSuccessResult();
return ResultGenerator.genFailResult("系统异常...");
}
}
三、总结
新增重试任务成功之后,我们可通过调度平台(比如:xxlJob),定时查询重试任务表,然后调用RetryTaskDefinitionEnum中定义的重试的service(retryServiceName),这里可以定义一个模板方法,根据retryServiceName,从spring中获取到对应的bean,执行具体的业务方法,然后更新任务状态和重试次数即可。