Spring Cloud Ribbon Retry机制
当使用Spring Cloud Ribbon实现客户端负载均衡的时候,通常都会利用@LoadBalanced来让RestTemplate具备客户端负载功能,从而实现面向服务名的接口访问。
•大多数情况下,上面的实现没有任何问题,但是总有一些意外发生,比如:有一个实例发生了故障而该情况还没有被服务治理机制及时的发现和摘除,这时候客户端访问该节点的时候自然会失败。所以,为了构建更为健壮的应用系统,我们希望当请求失败的时候能够有一定策略的重试机制,而不是直接返回失败。这个时候就需要开发人员人工的来为上面的RestTemplate调用实现重试机制。
不过,从Spring Cloud Camden SR2版本开始,我们就不用那么麻烦了。从该版本开始,Spring Cloud整合了Spring Retry来实现重试逻辑,而对于开发者只需要做一些配置即可。
•简单配置ribbon与retry重试机制:
•对于ConnectTimeout与ReadTimeout这两个配置,底层代码似乎有bug,代码发现并没有对超时处理进行生效,所以我建议使用RestTemplate原始配置即可。
•hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试。
模拟原生retry:
1.添加依赖(pom):
<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>
<!-- 引入spring原生retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- 目前版本需要引入aspectj,否则启动异常 -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-02-retry</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.cc.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.此次使用application.yml方式配置:
spring:
application:
name: retry-service
server:
context-path: /
port: 7004
eureka:
client:
service-url:
defaultZone: http://eureka1:8001/eureka
3.创建UserService:
package com.cc.springcloud.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 {
/**
* 需要进行重试的方法
*加@Retryabl注解
*value= {}数组,可以添加多个参数,(如果重试失败会根据具体异常来调用具体降级方法)
*RemoteAccessException.class远程方法调用异常时重试
*/
//@Retryable(value= {RemoteAccessException.class}, //需要在捕获什么异常的情况下进行重试
@Retryable(value= {RemoteAccessException.class,NullPointerException.class},
maxAttempts = 3, //重试次数
//重试策略,通过@Backoff注解加入相关策略:delay=4000重试间隔时间(重试延迟时间)为4秒,线程数:multiplier = 1单线程;
backoff=@Backoff(delay=4000,multiplier = 1)
)
public void call() throws Exception {
System.out.println("do something.....");
//抛出异常模拟重试
//throw new RemoteAccessException("Rpc调用异常.....");
throw new NullPointerException("空指针异常.....");
}
/**
* 如果依照规定的重试策略,重试3次依然失败
* 启动retry提供的降级策略;
* recover(RemoteAccessException e)参数即重试策略的条件,参数必须匹配
*/
@Recover
public void recover(RemoteAccessException e) {
System.out.println("最终处理结果1:"+e.getMessage());
}
//参数与重试策略不对应不会运行
//@Retryable(value= {RemoteAccessException.class,NullPointerException.class}, 如果添加入参数数组,就可运行降级
@Recover
public void recover(NullPointerException e) {
System.out.println("最终处理结果2:"+e.getMessage());
}
}
4.创建Application:
package com.cc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.retry.annotation.EnableRetry;
@EnableDiscoveryClient //标示是一个具体的服务,需要向注册中心注册
@SpringBootApplication //springboot 核心配置
@EnableRetry //使用spring原生retry必须开启此注解,即开启重试
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.创建测试用例:
package com.cc.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.cc.springcloud.Application;
//单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class RetryTests {
@Autowired
private UserService userService;
@Test
public void retryTest() throws Exception {
userService.call();
}
}