介绍:
- 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>
启动类上加注解
@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;
@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;
@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));
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));
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) {
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;
@Data
public class RetryTransferBO implements Serializable {
private static final long serialVersionUID = -1772838344464666024L;
private Integer count;
private Result result;
public boolean checkFailResult() {
return result == null || result.getIsSuccess() == null || !result.getIsSuccess();
}
}