当我们使用Spring Cloud Ribbon实现客户端负载均衡的时候,通常都会利用@LoadBalanced来让RestTemplate具备客户端负载功能,从而实现面向服务名的接口访问。 大多数情况下,上面的实现没有任何问题,但是总有一些意外发生,比如:有一个实例发生了故障而该情况还没有被服务治理机制及时的发现和摘除,这时候客户端访问该节点的时候自然会失败。所以,为了构建更为健壮的应用系统,我们希望当请求失败的时候能够有一定策略的重试机制,而不是直接返回失败。这个时候就需要开发人员人工的来为上面的RestTemplate调用实现重试机制。 不过,从Spring Cloud Camden SR2版本开始,我们就不用那么麻烦了。从该版本开始,Spring Cloud整合了Spring Retry来实现重试逻辑,而对于开发者只需要做一些配置即可。
注意:一半重试时间要不超过断路器时间
pom文件引入jar:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>com.zx.dt2b.erp</artifactId>
<groupId>com.zx.dt2b.erp</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>erp</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 标识这个工程是一个服务,需要引入此jar -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 动态刷新的一个模块jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入ribbon组件负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--重试策略-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
<!--eureka版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- <version>Dalston.SR5</version> -->
<version>Edgware.SR4</version>
<!-- <version>Finchley.SR1</version> -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>erp</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.zx.dt2b.ErpApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件:
##针对于某一个微服务进行重试策略配置: erp-service为服务
##对所有的请求都进行重试
erp-service.ribbon.OkToRetryOnAllOperations=true
##切换实例的次数
erp-service.ribbon.MaxAutoRetriesNextServer=1
##对当前实例重试的次数
erp-service.ribbon.MaxAutoRetries=2
spring.application.name=erp-service
server.context-path=/erpservice
server.port=7003
##需要引入eureka注册中心的地址
eureka.instance.prefer-ip-address=true
#注册后能显示自己的ip地址
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${server.port}
##租期更新时间间隔
eureka.instance.lease-renewal-interval-in-seconds=10
##租期的到期时间间隔
eureka.instance.lease-expiration-duration-in-seconds=30
##开启健康检查(必须要引入spring-boot-starter-actuator)
eureka.client.healthcheck.enabled=true
#注册到对应注册中心
eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
重试策略方法:
出现各种异常时候重试:
注解
@Retryable
其中:
value = {RemoteAccessException.class, NullPointerException.class}, //需要在捕获什么异常的情况下进行重试
maxAttempts = 3, //重试的次数
backoff = @Backoff(delay = 4000, multiplier = 1)//重试延迟4秒钟执行multiplier:1 单线程
注解
@Recover//最后一次“对应异常”后需要执行的方法
上面出现的异常需要怎么处理
package com.zx.dt2b.service;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
//需要进行重试的方法
@Retryable(value = {RemoteAccessException.class, NullPointerException.class}, //需要在捕获什么异常的情况下进行重试
maxAttempts = 3, //重试的次数
backoff = @Backoff(delay = 4000, multiplier = 1)
)
public void call() throws Exception {
System.err.println("do something.........");
throw new NullPointerException("空指针异常..");
//throw new RemoteAccessException("RPC调用异常..");
}
@Recover
public void recover(RemoteAccessException e) {
System.err.println("最终处理的结果1: " + e.getMessage());
}
@Recover
public void recover(NullPointerException e) {
System.err.println("最终处理的结果2: " + e.getMessage());
}
}
用测试用例测试service即可
package com.bfxy.springcloud.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.bfxy.springcloud.Application;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class RetryTests {
@Autowired
private UserService userService;
@Test
public void retryTest() throws Exception {
//userService.call();
}
}