文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Springboot如何优雅的实现异常重试机制

2024-11-30 06:37

关注

一、概述

微服务之间相互调用,难免会出现形形色色的异常,出现异常时有些情况可能需要先落重试任务表,然后通过任务调度等进行定时重试;通过自定义重试注解@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;

    
    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);
}




    
        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})
    

重试任务表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;

    
    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,执行具体的业务方法,然后更新任务状态和重试次数即可。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯