微服务之间相互调用,难免会出现形形色色的异常,出现异常时有些情况可能需要先落重试任务表,然后通过任务调度等进行定时重试;通过自定义重试注解@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):
@Datapublic 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);}
@Servicepublic 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@Documentedpublic @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@Componentpublic 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,执行具体的业务方法,然后更新任务状态和重试次数即可。
本文链接:http://www.28at.com/showinfo-26-15322-0.htmlSpringboot如何优雅的实现异常重试机制
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com