以上一篇【SpringCloud入门(二):Restful API-RestTemplate(Greenwich.SR2)】为基础来继续。
我们知道RestTemplate是负责服务与服务之间的通讯的,但在微服务中,一般上服务都不会进行单点部署的,都会至少部署2台及以上的。现在我们有了注册中心进行服务列表的维护,就需要一个客户端负载均衡来进行动态服务的调用。
何为负载均衡
负载均衡(Load Balance)是分布式系统架构设计中必须考虑的因素之一,它通常是指,将请求/数据【均匀】分摊到多个操作单元上执行,负载均衡的关键在于【均匀】。
Ribbon
Spring Cloud Ribbon是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon实现的。与Eureka配合使用时,Ribbon 可自动从Eureka Server (注册中心) 获取服务提供者地址列表,并基于负载均衡算法,通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用。
服务提供者
在microservice-provider模块中,新增getRibbonBalancer接口。启动8764和8765两个端口。
idea多端口方式之一,-Dserver.port=8765
/**
* @describe Ribbon负载均衡测试
* @author: hero良
* @param
* @return:
*/
@GetMapping("/getRibbonBalancer")
public String getRibbonBalancer(String name){
log.debug("************接受到请求*************");
return "hello !" + name+" , this is 8764";
}
两个实例都已经注册到注册中心
服务消费者,分两种情况
- 项目使用了eureka
- 项目未使用eureka
1.使用eureka
pom依赖
<!--使用了eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml
spring:
application:
name: ribbon
server:
port: 8766
eureka:
client:
service-url:
#单机版
defaultZone: http://eureka1:8761/eureka/
#集群模式
# defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/,http://eureka3:8763/eureka/
2.未使用eureka
需要引入ribbon依赖
<!--项目未使用eureka-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<!--</dependency>-->
yml
provider-service:
ribbon:
# 未使用eureka或者禁用从注册中心获取服务提供者信息时,需要自己维护地址 请求的服务地址,ip:port,多个时使用 逗号 隔开
listOfServers: 127.0.0.1:8764,127.0.0.1:8765
在RestTemplate配置Bean新增 @LoadBalanced
@Bean
@LoadBalanced //开启负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
这里直接使用服务名调用
public String getRibbonBalancer(String name){
return restTemplate.getForObject("http://provider-service/appController/getRibbonBalancer?name="+name, String.class);
}
/**
* @description ribbon的负载均衡
* @author hero良
* @param name
* @return java.lang.String
* @exception
* @version 1.0
*/
@GetMapping("/getRibbonBalancer")
public String getRibbonBalancer(String name){
return ribbonService.getRibbonBalancer(name);
}
使用postman来测试接口8764和8765交替出现,因为Ribbon的负载策略默认是轮询
在yml中类修改负载策略,也可以使用Bean的方式来配置。
provider-service:
ribbon:
#负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
provider-service 表示局部配置,只针对这个服务有效,也可以使用全局配置,如下
ribbon:
#负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
Ribbon的超时、重试
RestTemplate结合Ribbon时,超时需要配置RestTemplate的超时,配置Ribbon时无效。
@Bean
@LoadBalanced //开启负载均衡注解
public RestTemplate restTemplate(){
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(500);
httpRequestFactory.setReadTimeout(2000);
return new RestTemplate(httpRequestFactory);
}
//无效
serviceId:
ribbon:
ReadTimeout: 1000
ConnectTimeout: 1000
Ribbon通过增加Spring-retry还有相关配置开启了重试,这个重试机制对于OpenFeign是不起作用的,但是对于 @LoadBalanced注解修饰的RestTemplate是有作用的。
<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<!--重试需要此依赖-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
yml配置
provider-service:
ribbon:
#负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
# 同一实例最大重试次数,不包括首次调用
MaxAutoRetries: 1
# 重试其他实例的最大重试次数,不包括首次所选的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都进行重试
#当OkToRetryOnAllOperations设置为false时,只会对get请求进行重试。
#如果设置为true,便会对所有的请求进行重试,如果是put或post等写操作,
#如果服务器接口没做幂等性,会产生不好的结果,所以OkToRetryOnAllOperations慎用。
OkToRetryOnAllOperations: false
新增getRibbonTimeout接口
/**
* @description ribbon的超时与重试
* 超时需要在创建RestTemplate的时候指定时间,配置在配置文件中不生效,重试需要引入Spring-retry依赖
* @author hero良
* @return java.lang.String
* @exception
* @version 1.0
*/
@GetMapping("/getRibbonTimeout")
public String getRibbonTimeout(){
return restTemplate.getForObject("http://provider-service/appController/getRibbonTimeout", String.class);
}
服务提供者
这里使用了sleep,让线程阻塞,达到超时重试的效果,sleep的时间要比设置的超时时间大,不然等于没设置。
/**
* @describe Ribbon超时测试
* @author: hero良
* @param
* @return:
*/
@GetMapping("/getRibbonTimeout")
public String getRibbonTimeout(){
log.debug("************接受到请求*************");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}
调用后我们可以看到服务端的日志输出
两个服务加起来一共是6次请求,既实现了负载均衡也实现了 超时重试
重试计算公式
MaxAutoRetries+MaxAutoRetriesNextServer+(MaxAutoRetries * MaxAutoRetriesNextServer) ,即重试5次 (不包括首次调用)一共产生6次调用。