SpringRetry异常重试

本文介绍了如何在Spring框架中使用SpringRetry进行微服务间接口的重试,包括pom.xml引入、启动类注解配置、@Retryable方法应用以及配置文件的使用。实例展示了如何处理feign接口的异常,以及如何使用@Recover进行错误处理。
摘要由CSDN通过智能技术生成

介绍:

  • spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断。当某一次网络请求失败时,能够重新进行尝试请求,解决某一时间的网络问题导致整个请求失败。
  • 常见应用场景:
    • (1)微服务之间的feign接口retry
    • (2)微服务与第三方服务之间API对接的retry
    • (3)服务与database之间的retry

pom引入

        <dependency>
            <artifactId>spring-retry</artifactId>
            <groupId>org.springframework.retry</groupId>
            <version>${spring.retry.version}</version>
        </dependency>
-- spring.retry 版本号
<spring.retry.version>1.2.5.RELEASE</spring.retry.version>

启动类上加注解

// 启动Retry
@EnableRetry
// 启用异步 因为我使用的是异步的模式 
@EnableAsync

需要重试的方法上加@Retryable

    // 基础写法, 但是 生产肯定要抽取到配置文件中
    @Retryable(value= {BusinessException.class},maxAttempts = 5,backoff = @Backoff(delay = 5000l,multiplier = 1))
    // 抽取到配置文件写
    @Retryable(value = BusinessException.class, maxAttemptsExpression = "${retry.attempts}", backoff = @Backoff(delayExpression = "${retry.delay}", multiplierExpression = "${retry.multiplier}"))
 
 //配置文件
retry:
  # 重试次数
  attempts: 3
  # 重试延迟时间
  delay: 1000
  # 重试延迟倍率
  multiplier: 0
#参数解释:
@Retryable 标注此注解的方法在发生异常时会进行重试
            value:指定处理的异常类

            include:指定处理的异常类和value一样,默认为空,当exclude也为空时,默认所有异常

            exclude:指定异常不处理,默认空,当include也为空时,默认所有异常

            maxAttempts:最大重试次数。默认3次

            backoff: 重试等待策略。默认使用@Backoff注解

@Backoff 重试等待策略
            不设置参数时,默认使用FixedBackOffPolicy(指定等待时间),重试等待1000ms

            设置delay,使用FixedBackOffPolicy(指定等待时间),重试等待填写的时间

            设置delay和maxDealy时,重试等待在这两个值之间均态分布

          设置delay、maxDealy、multiplier,使用 ExponentialBackOffPolicy(指数级重试间隔的实现 ),multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。,比如delay=5000l,multiplier=2,则第一次重试为5秒,第二次为10秒,第三次为20秒
……

@Recover 用于@Retryable重试失败后处理方法,此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别,可以在该方法中进行日志处理。

注: 
1.当同步的时候代码执行完重试方法会继续执行.所以异步的后续逻辑要写到重试方法里面,比如发送结果的处理.
2.由于Spring Retry是采用AOP实现的重试机制,需要避免方法内部调用,会使aspect增强失效

代码示例

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @version 1.0  发送 重试实现类
 * @description: ReceiveBackRetryService
 * @author: 单人影
 * @create: 2021-06-28 09:52
 **/
@Service
@Slf4j
public class SendMsgLogic {

    @Value("${retry.attempts}")
    private int retryTimes;
    @Value("${fdepUrl}")
    private String fdepUrl;
    @Value("${encryFlag}")
    private String encryFlag;
    @Value("${spring.application.name}")
    private String appName;
 

    /**
     * @description: 异步发送
     * @param: [msg804BO]
     * @return: void
     * @author: 单人影
     * @date: 2021/6/28 10:13
     * @version: 1.0
     */
    @Retryable(value = BusinessException.class, maxAttemptsExpression = "${retry.attempts}", backoff = @Backoff(delayExpression = "${retry.delay}", multiplierExpression = "${retry.multiplier}"))
    @Async
    public void send(BcmTrustSendMsgFlowDO flowDO, RetryTransferBO retryTransferBO) {
        log.info("发送消息编号:{}.重试信息:{}", flowDO.getMsgNo(), JacksonUtils.objectToString(retryTransferBO));
        //1.feign 调用 发送信息
        String sendMessage;
        if (YesOrNoEnums.YES.getCode().equals(encryFlag)) {
            sendMessage = BcmEncodeUtils.toZipEncode(flowDO.getMsgContent());
        } else {
            sendMessage = flowDO.getMsgContent();
        }
        SendMessageRequest sendMessageRequest = new SendMessageRequest();
        sendMessageRequest.setMessage(sendMessage);
        sendMessageRequest.setAppName(appName);
        log.info("发送fdep信息,请求地址:{},请求参数:{}", fdepUrl, JacksonUtils.objectToString(sendMessageRequest));
        // feign异步调用
        Result voidResult = FdepSendApiUtil.sendByHttp(fdepUrl, sendMessageRequest);
        log.info("发送信息请求参数:{},响应结果:{}", sendMessage, JacksonUtils.objectToString(voidResult));
        if (retryTransferBO.getCount() == null) {
            retryTransferBO.setCount(0);
        }
        retryTransferBO.setCount(retryTransferBO.getCount() + 1);
        retryTransferBO.setResult(voidResult);
        if (ResultUtils.checkVoidFail(voidResult)) {
            // 重试日志
            log.error("发送报文请求失败,请求参数:{},响应结果:{}", JacksonUtils.objectToString(flowDO),
                    JacksonUtils.objectToString(voidResult));
            throw new BusinessException(ErrorEnums.BCM05039999);
        } else {
        // 发送成功处理逻辑
            if (flowDO.getRetryTimes() != null) {
                flowDO.setRetryTimes(flowDO.getRetryTimes() + 1);
            }
            flowDO.setSendStatus(SendStatusEnums.SUCCESS.getCode());
            flowDO.setSendTime(DateUtils.sysdateTo5());
            bcmTrustSendMsgFlowLogic.updateById(flowDO);
            if (SendEnums.CODE804.getCode().equals(flowDO.getIntNum())) {
                log.info("发送消息回执成功,处理原订单回执状态:{}", flowDO.getMsgContent());
                Msg804BO msg804BO = JacksonUtils.stringToObject(flowDO.getMsgContent(), Msg804BO.class);
                BcmTrustReceiveMsgFlowDO oneByMsgNo = bcmTrustReceiveMsgFlowLogic
                        .getOneByMsgNo(msg804BO.getReturnMsgNo());
                if (oneByMsgNo == null) {
                    //原消息未通过校验 就不会落库
                    log.warn("根据回执流水号查询原消息失败:{}", msg804BO.getReturnMsgNo());
                } else {
                    log.info("根据消息id:{}.原消息状态:{}", oneByMsgNo.getId(), oneByMsgNo.getReturnStatus());
                    oneByMsgNo.setReturnStatus(ReceiveReturnStatusEnums.SUCCESS.getCode());
                    bcmTrustReceiveMsgFlowLogic.updateById(oneByMsgNo);
                }
            }
        }
        log.info("发送消息编号:{} 结束", flowDO.getMsgNo());
    }

    @Recover
    public RetryTransferBO recover(BusinessException exception, BcmTrustSendMsgFlowDO sendMsgFlowDO,
            RetryTransferBO retryTransferBO) {
        //回调  发送回执失败Spring Retry
        log.error("发送消息失败:{}", JacksonUtils.objectToString(sendMsgFlowDO));
        sendMsgFlowDO.setRetryTimes(retryTimes);
        sendMsgFlowDO.setSendStatus(ReceiveReturnStatusEnums.FAIL.getCode());

        bcmTrustSendMsgFlowLogic.updateById(sendMsgFlowDO);
//发送失败报警信息
        alertMessageLogic.sendFail(sendMsgFlowDO);
        return retryTransferBO;
    }
}
import java.io.Serializable;
import lombok.Data;

/**
 * @description: RetryTransferBO 类只是我 为了记录当前是第几次重试,每次重试的信息结果
 * @author: 单人影
 * @date: 2021/6/28 13:14
 * @version: 1.0
 */
@Data
public class RetryTransferBO implements Serializable {

    private static final long serialVersionUID = -1772838344464666024L;
    /**
     * 重试次数
     */
    private Integer count;
    /**
     * feign请求返回类
     */
    private Result result;

    /**
     * feign返回检查 是否成功
     *
     * @return
     */
    public boolean checkFailResult() {
        return result == null || result.getIsSuccess() == null || !result.getIsSuccess();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值