Ribbon负载均衡有好几种方案,本文介绍的是Ribbon+RestTemplate的方案。但RestTemplate使用起来不太方便,实际开发中我们一般用OpenFeign方案,所以本文侧重于Ribbon的配置,RestTemplate一笔带过。
负载均衡为了提高服务的可用性,我们一般会将一个服务部署到多台服务器上,形成多个实例,负载均衡的作用就是把请求均衡的分配到各个实例中。负载均衡一般分为服务端负载均衡和客户端负载均衡,服务端的负载均衡通过硬件(如F5)或者软件(如Nginx)来实现,而Ribbon实现的是客户端负载均衡。服务端负载均衡是在硬件设备或者软件模块中维护一份可用服务清单,然后客户端发送服务请求到这些负载均衡的设备上,这些设备根据一些算法均衡的将请求转发出去。而客户端负载均衡则是客户端自己从服务注册中心(如之前提到的Eureka Server)中获取服务清单缓存到本地,然后通过Ribbon内部算法均衡的去访问这些服务。
开始配置配置服务提供者
这里我们配置两个服务提供者,服务提供者只需提供一个http接口,并注册到Eureke或者其他注册中心即可,具体配置可参考SpringCloud Eureka教程
@RestControllerpublic class Controller {@RequestMapping("/getUser")public String getUser() throws InterruptedException {//返回实例端口号,用于区分ribbon访问的是哪个实例return "8082"; } }复制代码
另一个服务提供者,注意这里开启了线程休眠,时间是3秒,用于测试Ribbon的超时重试功能。
@RestControllerpublic class Controller {@RequestMapping("/getUser")public String getUser() throws InterruptedException { System.out.println("getUser"); TimeUnit.SECONDS.sleep(3);return "8084"; } }复制代码
服务消费者配置
相关依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <!--里面提供了restTemplate--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>复制代码
服务消费者通常只需注册到Eureka或者其他注册中心即可,并在Controller层或者Service层使用RestTemplate访问服务提供者的接口,这种访问是带有负载均衡功能的。
Ribbon配置
注入RestTemplate
将这一段代码放进启动类或者配置类中
@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate(); }复制代码
例如把它放到启动类中
@EnableEurekaClient@SpringBootApplicationpublic class RibbonConsumerApplication {public static void main(String[] args) { SpringApplication.run(RibbonConsumerApplication.class, args); }@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate(); } }复制代码
如果不配置这一步,是无法通过@AutoWrite引入RestTemplate的。
通过RestTemplate访问接口
@RestControllerpublic class Controller {@Autowiredprivate RestTemplate restTemplate;@GetMapping("/getUser")public String getUser() throws InterruptedException { //注意这里用的是服务名,而不是具体的地址、端口return restTemplate.getForObject("http://provider/getUser",String.class); } }复制代码
配置文件
- 开启client或restclient,否则超时重试无效
# client/restclient选择其中一个设置为true即可,有人说client已被废弃,应该用restclient,这种说法不知道对不对 ribbon: http: client: enabled: true restclient: enabled: true复制代码
- 开启重试
spring: cloud: loadbalancer: retry: enabled: true复制代码
- 配置超时时间和重试次数
provider: #服务名,针对某服务的配置,全局配置去掉这个就行,也就是直接ribbon.*** ribbon: #连接超时时间(单位:毫秒) ConnectTimeout: 100#读取超时时间(单位:毫秒) ReadTimeout: 100#负载均衡策略,如果用Ribbon自带的,只需更改最后一段的名字,前面的包名不用改 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #所有请求失败后都进行重试 OkToRetryOnAllOperations: true#切换实例的重试次数 MaxAutoRetriesNextServer: 2#在本实例上的重试次数 MaxAutoRetries: 2复制代码
- ConnectTimeout和ReadTimeout的区别在于:ConnectTimeout是和服务器建立连接的超时时间,ReadTimeout是建立连接后从服务器读取资源的超时时间。
- MaxAutoRetries和MaxAutoRetriesNextServer的区别在于:请求超时后,先在当前实例上进行重试,次数由MaxAutoRetries决定,如果仍然失败,切换实例进行重试,切换次数由MaxAutoRetriesNextServer决定,如果还是失败,就报错。所以总重试次数等于MaxAutoRetries+MaxAutoRetriesNextServer
Ribbon自带的负载均衡策略
策略 | 命名 | 描述 | 实现说明 |
---|---|---|---|
RandomRule | 随机策略 | 随机选择可用的服务器 | 在 index 上随机,选择 index 对应位置的服务器 |
RoundRobinRule | 轮询策略 | 按顺序循环选择服务器 | 轮询 index,选择 index 对应位置的服务器 |
RetryRule | 重试策略 | 对选定的负载均衡策略加上重试机制 | 在一个配置时间段内当选择服务器不成功,则一直尝试使用 subRule 的方式选择一个可用的服务器 |
BestAvailableRule | 最低并发策略 | 选择一个并发请求量最少的服务器 | 逐个考察服务器,如果服务器的断路器打开,则忽略,再选择其他并发连接数最低的服务器 |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直连接失败并被标记为 circuit tipped 的服务器,过滤掉那些高并发连接的服务器(active connections 超过配置的阀值) | 使用一个 AvailabilityPredicate 来包含过滤服务器的逻辑,其实就是检查 status 里记录的各个服务器的运行状态 |
WeightedResponseTimeRule | 响应时间加权策略 | 根据服务器的响应时间分配权重。响应时间越长,权重越低,被选择到的概率越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、CPU 等,这些因素都直接影响着响应时间 | 一个后台线程定期的从 status 里面读取评价响应时间,为每个服务器计算一个权重;其中权重的计算也比较简单,responsetime 减去每个服务器自己平均的 responsetime 就是服务器的权重。当刚开始运行,没有形成 status 时,使用轮询策略选择服务器 |
ZoneAvoidanceRule | 区域权衡策略 | 综合判断服务器所在区域的性能和服务器的可用性来轮询选择服务器,并且判断一个 Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有服务器 | 使用 ZoneAvoidancePredicate 和 AvailabilityPredicate 来判断是否选择某个服务器,前一个判断判定一个 zone 的运行性能是否可用,剔除不可用的 zone 的所有服务器,AvailabilityPredicate 则用于过滤掉连接数过多的服务器 |
Eureka的注册情况
访问消费者接口
本文用的是随机策略
- 访问到8082实例
- 访问到8084实例,重试失败
8084实例内部休眠了3秒钟,所以会超时,重试仍然失败后就会显示这个错误。
- 访问到8084实例,重试成功
注意看请求列表中600ms左右的请求,这两个就是重试成功的请求,下面20ms左右的请求是访问8082实例的请求,重试失败的时间是900ms左右。