Ribbon
简介
Ribbon是Netflix的一个开源组件,Spring进行了封装,可用实现应用级别的负载均衡、请求重发等功能。
Ribbon负载均衡
应用场景:在高并发请求场景下,为了减轻单个服务节点的压力,将流量分流到不同应用节点,就需要用到负载均衡。
原理:ribbon组件结合RestTemplate
来实现负载均衡,只需给restTemplate
加上@LoadBalanced
注解就可以实现。ribbon是根据服务名称application.name
来做负载均衡的。
搭建EurekaServer
-
配置pom.xml
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <!-- 2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io --> <version>Hoxton.SR11</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <!-- jar或war包名称 --> <finalName>springcloud-ribbon-eureka-server</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
-
配置application.properties
# 应用名称 spring.application.name=eureka-server # 服务端口 server.port=8001 # eureka的主机名称 eureka.instance.hostname=eureka1 # 客户端配置-注册中心访问地址, eureka1是在Hosts配置的映射,替代域名的作用。 eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
-
添加启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class SpringcloudRibbonEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudRibbonEurekaServerApplication.class, args); } }
搭建EurekaClient-Provider
-
配置pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <!-- 2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io --> <version>Hoxton.SR11</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <finalName>springcloud-ribbon-eureka-provider</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
-
配置application
server.port=7001 spring.application.name=provider-service # 注册到eureka显示IP eureka.instance.prefer-ip-address=true # 租期到期间隔,eureka服务器从收到最后一次心跳开始等待的时间(以秒为单位); 该值必须至少高于“leaseRenewalIntervalInSeconds”中指定的值 eureka.instance.lease-expiration-duration-in-seconds=10 # 租期更新间隔,eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位) eureka.instance.lease-renewal-interval-in-seconds=5 # 开启健康监测, 必须引入spring-boot-starter-actuator的jar包 eureka.client.healthcheck.enabled=true # 注册中心地址 eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
-
添加启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class SpringcloudRibbonEurekaProviderApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudRibbonEurekaProviderApplication.class, args); } }
-
添加接口服务
import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @RestController public class UserController { private static List<User> users = new ArrayList<>(); @GetMapping("getUser") public User getUser(@RequestParam String id) throws InterruptedException { log.info("provider-service ------getUser 获取用户信息--------id: {}",id); TimeUnit.SECONDS.sleep(2); return new User("001","张三",18); } @PostMapping("postUser") public List<User> postUser(@RequestBody User requestUser) throws InterruptedException { log.info("provider-service ------postUser 获取用户信息--------requestUser: {}", JSON.toJSONString(requestUser)); TimeUnit.SECONDS.sleep(2); users.add(requestUser); return users; } }
搭建EurekaClient-Provider-Second
同理,按照搭建EurekaClient-Provider的方式再搭一套服务提供者,这两个服务用同样的服务名称。通过端口号来区分开即可。
-
配置pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <!-- 2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io --> <version>Hoxton.SR11</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <finalName>springcloud-ribbon-eureka-provider-second</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
-
配置application
server.port=7002 spring.application.name=provider-service # 注册到eureka显示IP eureka.instance.prefer-ip-address=true # 租期到期间隔,eureka服务器从收到最后一次心跳开始等待的时间(以秒为单位); 该值必须至少高于“leaseRenewalIntervalInSeconds”中指定的值 eureka.instance.lease-expiration-duration-in-seconds=10 # 租期更新间隔,eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位) eureka.instance.lease-renewal-interval-in-seconds=5 # 开启健康监测, 必须引入spring-boot-starter-actuator的jar包 eureka.client.healthcheck.enabled=true # 注册中心地址 eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
-
添加启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class SpringcloudRibbonEurekaProviderSecondApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudRibbonEurekaProviderSecondApplication.class, args); } }
-
添加接口服务
import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @RestController public class UserController { private static List<User> users = new ArrayList<>(); @GetMapping("getUser") public User getUser(@RequestParam String id) throws InterruptedException { log.info("provider-second-service ------getUser 获取用户信息--------id: {}",id); TimeUnit.SECONDS.sleep(2); return new User("001","张三",18); } @PostMapping("postUser") public List<User> postUser(@RequestBody User requestUser) throws InterruptedException { log.info("provider-second-service ------postUser 获取用户信息--------requestUser: {}", JSON.toJSONString(requestUser)); TimeUnit.SECONDS.sleep(2); users.add(requestUser); return users; } }
搭建EurekaClient-Consumer
-
配置pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <!-- 2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io --> <version>Hoxton.SR11</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <finalName>springcloud-ribbon-eureka-consumer</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
-
配置application
server.port=7003 spring.application.name=consumer-service # 注册到eureka显示IP eureka.instance.prefer-ip-address=true # 租期到期间隔,eureka服务器从收到最后一次心跳开始等待的时间(以秒为单位); 该值必须至少高于“leaseRenewalIntervalInSeconds”中指定的值 eureka.instance.lease-expiration-duration-in-seconds=10 # 租期更新间隔,eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位) eureka.instance.lease-renewal-interval-in-seconds=5 # 开启健康监测, 必须引入spring-boot-starter-actuator的jar包 eureka.client.healthcheck.enabled=true # 注册中心地址 eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
-
添加启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient public class SpringcloudRibbonEurekaConsumerApplication { @Bean // 交给spring管理 @LoadBalanced // 开启负载均衡 public RestTemplate restTemplate(){ HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(); httpComponentsClientHttpRequestFactory.setConnectTimeout(10000); httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(10000); httpComponentsClientHttpRequestFactory.setReadTimeout(20000); return new RestTemplate(httpComponentsClientHttpRequestFactory); } public static void main(String[] args) { SpringApplication.run(SpringcloudRibbonEurekaConsumerApplication.class, args); } }
启动测试
启动所有服务,8001端口服务是Eureka注册中心,7001和7002是模拟同一个服务提供者,7003是服务消费者。
我们来访问注册中心验证一下
访问consumer接口来观察provider控制台打印信息,测试负载均衡:
访问三次http://localhost:7003/get接口
consumer控制台如下:
2021-06-18 16:02:56.914 INFO 66739 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:07:56.920 INFO 66739 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:12:56.930 INFO 66739 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:15:12.584 INFO 66739 --- [nio-7003-exec-1] c.e.s.c.r.e.c.api.ConsumerController : 成功获取到数据:{"id":"001","name":"张三","age":18}
2021-06-18 16:15:16.285 INFO 66739 --- [nio-7003-exec-2] c.e.s.c.r.e.c.api.ConsumerController : 成功获取到数据:{"id":"001","name":"张三","age":18}
2021-06-18 16:15:19.572 INFO 66739 --- [nio-7003-exec-3] c.e.s.c.r.e.c.api.ConsumerController : 成功获取到数据:{"id":"001","name":"张三","age":18}
provider控制台如下:
2021-06-18 16:06:02.594 INFO 68778 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:11:02.600 INFO 68778 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:15:14.259 INFO 68778 --- [nio-7001-exec-1] c.e.s.c.r.e.provider.api.UserController : provider-service ------getUser 获取用户信息--------id: 001
2021-06-18 16:16:02.612 INFO 68778 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
Provider-second控制台如下:
2021-06-18 16:02:06.544 INFO 66614 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:07:06.553 INFO 66614 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:12:06.562 INFO 66614 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-18 16:15:10.577 INFO 66614 --- [nio-7002-exec-7] c.e.s.c.r.e.p.second.api.UserController : provider-second-service ------getUser 获取用户信息--------id: 001
2021-06-18 16:15:17.565 INFO 66614 --- [nio-7002-exec-8] c.e.s.c.r.e.p.second.api.UserController : provider-second-service ------getUser 获取用户信息--------id: 001
由上可知consumer访问3次接口,分别调用到了provider1次,provider-second2次,实现了负载均衡的效果。
Ribbon Retry重试机制
应用场景:通常在服务调用时可能服务提供者无法正常提供服务,正在重启或服务已经宕机,那么可以利用重试机制来重新发送请求,一直到服务可用,一定程度上保证业务可用。
Spring原生Retry
-
配置pom.xml
<!-- spring 原生retry组件 --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
-
编写重试接口
import lombok.extern.slf4j.Slf4j; 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; /** * spring原生retry组件测试, * 可以通过 * @see com.example.springcloud.ribbon.retry.SpringcloudRibbonRetryApplicationTests#retryTest() 测试类进行演示。 */ @Slf4j @Service public class UserService { @Retryable( value = {RemoteAccessException.class},// 发生这个异常进行重试 maxAttempts = 5,// 重试次数,默认3次 backoff = @Backoff(delay = 2000L,multiplier = 1) // 延迟3秒进行重试,单线程处理 ) public void run(){ log.info("--------run-------------"); throw new NullPointerException("执行逻辑空指针了..."); // throw new RemoteAccessException("远程调用失败..."); } /** * 最后一次失败后调用此方法 * @param e 异常 */ @Recover public void recover(RemoteAccessException e){ log.warn("RAE最终处理的结果是:"+e.getMessage()); } /** * 如果NullPointerException没用放到Retryable注解的value值内,获取到异常则不会重试,直接执行1次本方法。 * @param e 异常 */ @Recover public void recover(NullPointerException e){ log.warn("NPE最终处理的结果是:"+e.getMessage()); } }
-
编写测试类
import com.example.springcloud.ribbon.retry.service.UserService; import org.junit.jupiter.api.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; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringcloudRibbonRetryApplication.class) class SpringcloudRibbonRetryApplicationTests { @Autowired private UserService userService; /** * spring原生retry测试 * @throws Exception */ @Test void retryTest() throws Exception { userService.run(); } }
-
运行查看控制台:因为NullPointerException没有被配置Retryable所以发生异常时就直接调用
public void recover(NullPointerException e)
方法了。2021-06-19 06:12:12.402 INFO 77851 --- [ main] c.e.s.ribbon.retry.service.UserService : --------run------------- 2021-06-19 06:12:12.402 WARN 77851 --- [ main] c.e.s.ribbon.retry.service.UserService : NPE最终处理的结果是:执行逻辑空指针了...
-
接下来修改一下
UserService
的@Retryable
值,将NullPointerException也加到配置中,如下:@Retryable( value = {RemoteAccessException.class,NullPointerException.class},// 发生这些异常进行重试 maxAttempts = 5,// 重试次数,默认3次 backoff = @Backoff(delay = 2000L,multiplier = 1) // 延迟3秒进行重试,单线程处理 )
-
再次运行测试类:可以看到共进行了5次重试,每隔2秒1次,并且在最后一次重试后调用了
reconver()
方法。2021-06-19 06:16:38.956 INFO 77944 --- [ main] c.e.s.ribbon.retry.service.UserService : --------run------------- 2021-06-19 06:16:40.959 INFO 77944 --- [ main] c.e.s.ribbon.retry.service.UserService : --------run------------- 2021-06-19 06:16:42.961 INFO 77944 --- [ main] c.e.s.ribbon.retry.service.UserService : --------run------------- 2021-06-19 06:16:44.963 INFO 77944 --- [ main] c.e.s.ribbon.retry.service.UserService : --------run------------- 2021-06-19 06:16:46.968 INFO 77944 --- [ main] c.e.s.ribbon.retry.service.UserService : --------run------------- 2021-06-19 06:16:46.968 WARN 77944 --- [ main] c.e.s.ribbon.retry.service.UserService : NPE最终处理的结果是:执行逻辑空指针了...
接下来我们看一下SpringCloud Ribbon的Retry实现。
SpringCloud Ribbon Retry配置
因为cloud的retry是基于HTTP接口进行重试的,所以只要在注册到eureka服务上进行调用即可。
基本原理:
- 假设有providerA服、providerB服务、consumerC服务、eurekaServer服务等4个服务。
- consumerC去调用providerA和providerB,因为做了负载均衡,不确定调用到哪个提供者。
- 我们给consumerC配置HTTP读取超时时间为2秒(2秒内不返回结果认为连接超时)
- 同时让其中一个提供者的接口休眠4秒,另一个提供者正常提供服务。
- 访问consumerC的接口来测试是否有重试效果。
注意:在E版本之前,要防止hystix断路器超时时间导致retry失效。对于E版本之后,不需要考虑断路器的超时时间了
-
配置retry-service(消费端)的application.yml文件
spring: application: name: retry-service # 服务名称 cloud: loadbalancer: retry: enabled: true # 开启重试功能 server: port: 7004 # 服务端口 servlet: context-path: / # Eureka的相关配置 eureka: client: healthcheck: enabled: true # 开启健康监测 service-url: defaultZone: http://eureka1:8001/eureka # 注册中心地址 # 自定义配置:针对所有RestTemplate发起的HTTP请求做超时配置 custom: request-factory: connect-timeout: 10000 # HTTP连接超时时间 connection-request-timeout: 10000 # 请求连接超时时间 read-timeout: 2000 # HTTP读取时间,如果再2秒内没有得到结果,则认为超时 # 针对某个微服务进行重试策略配置 provider-service: ribbon: OkToRetryOnAllOperations: true # 对所有请求做重试 MaxAutoRetriesNextServer: 1 # 切换实例次数 MaxAutoRetries: 2 # 对当前实例进行重试次数
-
配置pom.xml
<parent> <groupId>com.example</groupId> <artifactId>springcloud-ribbon</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>springcloud-ribbon-retry</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <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> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <!-- 2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io --> <version>Hoxton.SR11</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <finalName>springcloud-ribbon-retry</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
-
添加启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.retry.annotation.EnableRetry; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient @EnableRetry // 原生spring retry组件 public class SpringcloudRibbonRetryApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudRibbonRetryApplication.class, args); } @Bean // 交给spring管理 @LoadBalanced // 开启负载均衡 public RestTemplate restTemplate(){ return new RestTemplate(httpComponentsClientHttpRequestFactory()); } /** * 读取自定义配置 * @return HttpComponentsClientHttpRequestFactory */ @ConfigurationProperties(prefix = "custom.request-factory") @Bean public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){ return new HttpComponentsClientHttpRequestFactory(); } }
-
编写API测试接口
import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @Slf4j @RestController public class RetryController { @Autowired private RestTemplate restTemplate; /** * 调用provider-service * @return */ @GetMapping("retry") public String retry(){ User user = restTemplate.getForObject("http://provider-service/getUser?id={1}", User.class,"001"); log.info("调用service结果:{}", JSON.toJSONString(user)); return "成功获取调用结果:"+user; } }
-
修改provider-service其中的一个接口(让方法耗时大于retry-service配置的HTTP读取超时时间,而触发重试机制去调用另一台provider-service )
@GetMapping("getUser") public User getUser(@RequestParam String id) throws InterruptedException { log.info("provider-service ------getUser 获取用户信息--------id: {}",id); TimeUnit.SECONDS.sleep(3);// 休眠3秒,retry-service的HTTP readTimeout为2秒 return new User("001","张三",18); }
-
启动测试
-
访问http://localhost:7004/retry 进行测试
retry-service控制台打印:最终获取到了调用结果,但是浏览器等待很久(因为进行了多次重试)。
2021-06-18 21:42:24.044 INFO 77045 --- [nio-7004-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: provider-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2021-06-18 21:42:24.064 INFO 77045 --- [nio-7004-exec-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-provider-service 2021-06-18 21:42:24.064 INFO 77045 --- [nio-7004-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: provider-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=provider-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 2021-06-18 21:42:24.068 INFO 77045 --- [nio-7004-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater 2021-06-18 21:42:24.085 INFO 77045 --- [nio-7004-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: provider-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2021-06-18 21:42:24.086 INFO 77045 --- [nio-7004-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client provider-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=provider-service,current list of Servers=[172.19.137.36:7001, 172.19.137.36:7002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:172.19.137.36:7002; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] , [Server:172.19.137.36:7001; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@5545ced6 2021-06-18 21:42:25.073 INFO 77045 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: provider-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2021-06-18 21:42:31.145 INFO 77045 --- [nio-7004-exec-1] c.e.s.ribbon.retry.api.RetryController : 调用service结果:{"age":18,"id":"001","name":"张三"}
provider-service1控制台:进行了3次重试,但每次都超时。
2021-06-18 21:42:24.112 INFO 76849 --- [nio-7001-exec-4] c.e.s.c.r.e.provider.api.UserController : provider-service ------getUser 获取用户信息--------id: 001 2021-06-18 21:42:26.120 INFO 76849 --- [nio-7001-exec-5] c.e.s.c.r.e.provider.api.UserController : provider-service ------getUser 获取用户信息--------id: 001 2021-06-18 21:42:28.122 INFO 76849 --- [nio-7001-exec-6] c.e.s.c.r.e.provider.api.UserController : provider-service ------getUser 获取用户信息--------id: 001
provider-service2控制台:被调用了1次,直接返回结果。
2021-06-18 21:42:30.124 INFO 76767 --- [nio-7002-exec-6] c.e.s.c.r.e.p.second.api.UserController : provider-second-service ------getUser 获取用户信息--------id: 001 2021-06-18 21:42:47.104 INFO 76767 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
好了,springcloud的retry就搞定了!
文中用到的代码示例下载:https://gitee.com/yunnasheng/springcloud-ribbon