JAVA && Spring && SpringBoot2.x — 学习目录
每篇一问
当我们调用一个接口时,可能因为网络波动造成了系统之间超时,此时我们应该怎么办?
重试呗~
是的,对于读操作或者支持幂等的写操作,一般我们便可以进行重试操作。那如何进行重试?
不用担心,Spring都处理好了...
正常逻辑和重试机制耦合度较高。基于这个些问题,spring-retry规范了了正常逻辑和重试逻辑,将正常逻辑和重试逻辑解耦。spring-retry是一个开源工具包,该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时,重试执行实例保证线程安全。spring-retry重试可以用Java代码实现也可以用注解@Retryable方式实现,这里spring-retry提倡以注解的方式对方法进行重试。
1、如何使用Spring-Retry进行重试
1. pom文件需要引入的依赖
<!-- SpringRetry重试机制 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2. 启动类上配置自动加载
在SpringBoot 引入新技术,一般需要2步:
(1)pom 文件加入依赖;
(2)启动类上加上@EnableXXX注解;
@SpringBootApplication
@EnableRetry //开启retry重试机制
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
3. @Retryable和@Recover的使用
@Service
public class RetryService {
//开启日志控制
private final static Logger logger = LoggerFactory.getLogger(RetryService.class);
private final int totalNum = 100;
/**
* value:出现Exception.class进行重试,指定特定的异常
* • include:和value一样,默认为空,当exclude也为空时,默认所以异常
* • exclude:指定不处理的异常
* •maxAttempts:最大重试次数,默认3次
* •backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L,
* 我们设置为2000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,
* 如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5
*
* @param num
* @return
*/
@Retryable(value = Exception.class, maxAttempts = 3,
backoff = @Backoff(delay = 2000L, multiplier = 1.5))
public int retry(int num) {
logger.info("{}减库存开始", LocalTime.now());
try {
int i = 1 / 0;
} catch (Exception e) {
logger.error("illegal");
}
if (num <= 0) {
throw new IllegalArgumentException("数量不对");
}
logger.info("减库存执行结束" + LocalTime.now());
return totalNum - num;
}
/**
* 作为降级方法调用的注释(重试调用完依旧失败,触发降级)。
* 参数(可选):合适的Throwable类型的第一个参数(或Throwable的子类型),若是参数为空,只有其他降级注释标注的方法均不复合时,才会执行改方法。(后续可以携带请求参数,但是Throwable必须为第一个参数)
* 此时该请求参数明确
* 返回值:@Retryable 方法类型相同的返回值。
*/
@Recover
public int recover(IllegalArgumentException e,int num) {
log.warn("recover减库存失败!!!" + LocalTime.now());
return totalNum;
}
/**
* 作为降级方法调用的注释(重试调用完依旧失败,触发降级)。
* 参数(可选):合适的Throwable类型的第一个参数(或Throwable的子类型),若是参数为空,只有其他降级注释标注的方法均不复合时,才会执行改方法。
* 返回值:@Retryable 方法类型相同的返回值。
*/
@Recover
public int recover2() {
log.warn("recover2减库存失败2222!!!" + LocalTime.now());
return totalNum;
}
4. 测试方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class RetryServiceTest {
@Autowired
RetryService retryService;
@Test
public void testRetry() {
int i = retryService.retry(-1);
System.out.println("数据是: "+ i );
}
}
5. 测试效果
@Retryable注解
被注解的方法发生异常时会重试
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有。
exceptionExpression:spEL表达式,若配置include或exclude参数那么该配置不会生效,其作用等效于include或exclude。格式#{@bean.methodName(#root)}
。methodName
的返回值为boolean类型。#root
是异常类,即用户可以在代码中判断是否进行重试。
@Retryable(maxAttempts = 2,
backoff = @Backoff(delay = 2000L, multiplier = 1.5),
exceptionExpression = "#{@retryService.xxx(#root)}")
public int retry(int num) {
log.info("{}减库存开始", LocalTime.now());
if (num <= 0) {
throw new RuntimeException("数量不对");
}
log.info("减库存执行结束" + LocalTime.now());
return totalNum - num;
}
public Boolean xxx(Exception e) {
//根据异常信息决定是否重试!!!
log.info("判断是否异常");
return true;
}
@Backoff注解
delay:指定延迟后重试
multiplier:指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒
@Recover
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。
下面咱们讲一下注意事项:
- 对于非幂等的读操作,应该禁止使用重试机制;
- 使用了@Retryable的方法不能在本类被调用(不能被套嵌使用),不然重试机制不会生效。由于retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效。
- 注解方法的异常不能通过try-catch捕获,应该throw出去,被捕获后处理,本质上就是AOP思想的使用。