负载均衡-Ribbon
客户端负载均衡
相比较于Nginx的服务端负载均衡,Ribbon属于客户端的负载均衡。客户端负载均衡和服务器负载均衡的核心差异在服务列表本身,客户端负载均衡服务列表是通过客户端自己维护的,而服务端负载均衡服务列表是由中间服务(例如Nginx)单独维护。Ribbon通过Eureka拿到所有服务列表,然后通过自身的负载均衡策略,选择一个服务去处理请求。
负载均衡策略
IRule:Ribbon所有的负载均衡策略类都需实现该接口。
策略类 | 命名 | 说明 |
---|---|---|
RandomRule | 随机策略 | 随机选择 Server |
RoundRobinRule | 轮训策略 | 按顺序循环选择 Server |
RetryRule | 重试策略 | 在一个配置时问段内当选择 Server 不成功,则一直尝试选择一个可用的 Server,超过时间则返回null |
BestAvailableRule | 最低并发策略 | 先过滤掉故障服务,然后它会基于过去30分钟的统计结果选取当前并发量最小的服务节点,也就是最“闲”的节点作为目标地址。如果统计结果尚未生成,则采用轮询的方式选定节点。 |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直连接失败并被标记为 circuit tripped 的 Server,过滤掉那些高并发连接的 Server(active connections 超过配置的网值) |
ResponseTimeWeightedRule | 响应时间加权策略 | 根据 Server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO等,这些因素直接影响着响应时间 |
ZoneAvoidanceRule | 区域权衡策略 | 综合判断 Server 所在区域的性能和 Server 的可用性轮询选择 Server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 Server |
在代码中使用 Ribbon
配置负载均衡策略
-
全局的配置方式,针对所有服务生效,添加Configuration配置类就行了
import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RibbonConfiguration { /** * 负载均衡策略-随机 * @return */ @Bean public IRule defaultLBStrategy() { return new RandomRule(); } }
-
局部的配置方式,正对指定的服务生效,有两种方式:
-
配置文件配置:
# 注意eureka-client是所针对的服务的名称 eureka-client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
-
通过注解配置 @RibbonClient:
import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Configuration; @Configuration @RibbonClient(name = "eureka-client", configuration = com.netflix.loadbalancer.RandomRule.class) public class RibbonConfiguration { }
-
@LoadBalanced 注解的使用
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.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate template() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
代码中使用RestTemplate发送请求,则可以在RestTemplate的配置方法上加上@LoadBalanced注解,这样当RestTemplate发送请求时将会使用Ribbon的负载均衡。
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;
@RestController
public class Controller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("sayHi")
public String sayHi() {
// 注意这里的请求地址不需要指明服务地址,只需要指明服务名称即可
// 这里就是负载均衡eureka-client服务,调用sayHi接口
return restTemplate.getForObject("http://eureka-client/sayHi", String.class);
}
}
IPing
IPing是Ribbon的一套healthcheck机制,故名思议,就是要Ping一下目标机器看是否还在线,一般情况下IPing并不会主动向服务节点发起healthcheck请求,Ribbon后台通过静默处理返回true默认表示所有服务节点都处于存活状态(和Eureka集成的时候会检查服节点UP状态)。
Ribbon的懒加载和饥饿加载
Ribbon默认是懒加载模式,首次加载发生在第一次调用的时候。
Ribbon还有一种加载模式是饥饿加载,会在项目启动时加载,需要添加配置:
# 开启Ribbon的饥饿加载模式,默认是懒加载(调用时才会初始化LoadBalancer)
ribbon.eager-load.enabled=true
# 指定需要应用饥饿加载的服务名称
ribbon.eager-load.clients=ribbon-consumer
服务间的通讯与调用-Feign
spring cloud服务之间的调用是基于HTTP的,然而对于原生的HTTP调用来说是件非常麻烦的事。Feign的出现就是为了解决这个问题,我们可以借助Feign的代理机制,像调用一个接口方法一样发起远程HTTP调用。
老的服务间调用方式:
@RestController
public class Controller {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/hello")
public String hello() {
// 获取eureka-client服务实例
ServiceInstance instance = loadBalancerClient.choose("eureka-client");
//调用eureka-client服务的sayHi接口
String target = String.format("http://%s:%s/sayHi", instance.getHost(), instance.getPort());
return restTemplate.getForObject(target, String.class);
}
}
使用feign后的调用方式:
@RestController
public class Controller {
@Autowired
private IService iService;
@GetMapping("hello")
public String hello() {
return iService.sayHi();
}
}
feign代码实践
pom.xml
<?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>spring-cloud-demo</artifactId>
<groupId>com.jinsh</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign-consumer</artifactId>
<packaging>jar</packaging>
<name>feign-consumer</name>
<dependencies>
<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-openfeign</artifactId>
</dependency>
<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>
</dependencies>
</project>
启动类加@EnableFeignClients注解,配置文件还和普通服务注册配置文件一样
package com.jinsh.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
IService 接口,添加@FeignClient("eureka-client")注解,eureka-client是想要调用的服务名称
package com.jinsh.springcloud;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient("eureka-client")
public interface IService {
@GetMapping("sayHi")
String sayHi();
}
controller中调用eureka-client服务的sayHi接口:
package com.jinsh.springcloud;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@Autowired
private IService iService;
@GetMapping("sayHi")
public String sayHi() {
return iService.sayHi();
}
}
feign使用讲解
引用
feign 中是包含ribbon和hystrix的,所以引用了feign后就不需要单独引用ribbon和hystrix了。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
注解
1、@EnableFeignClients
需要在消费者的服务启动类上使用@EnableFeignClients注解,消费者服务才能以feign方式调用其他服务接口,
如果被调用的服务接口包名与消费者服务包名不同,例如消费者服务的包名为com.jinsh.consumer,被调用方的服务包名为com.jinsh.client,那么EnableFeignClients注解上还需要添加被调用服务的包名@EnableFeignClients(basePackages = {"com.jinsh.client"})
。
2、@FeignClient
FeignClient注解添加在接口类上,注解上还需要声明被调用的服务名@FeignClient("feign-client")
,接口内则定义被调用服务的方法。
feign 的超时重试机制
feign 的超时重试机制底层是通过ribbon实现的。下面是配置超时重试的参数:
# feign-service-provider是服务提供者的服务名
feign-service-provider.ribbon.OkToRetryOnAllOperations=true
feign-service-provider.ribbon.ConnectTimeout=1000
feign-service-provider.ribbon.ReadTimeout=2000
feign-service-provider.ribbon.MaxAutoRetries=2
feign-service-provider.ribbon.MaxAutoRetriesNextServer=2
-
OkToRetryOnAllOperations:这个参数指定了什么HTTP Method可以进行Retry,这里为了演示方便才设置为true,表示不管GET还是POST什么都能重试。真实的生产环境往往只是GET请求可以重试,或者实现了幂等性的其他类型请求。
-
ConnectTimeout:超时判定的第一个参数(单位ms),创建会话的连接时间。注意,这个不是服务的响应时间,而是本机和服务建立一个Connection所花费的时间,如果连接超时则直接进行重试。
-
ReadTimeout:超时判定的第二个参数,服务响应时间。当连接建立好之后,如果对方服务没有在规定时间内返回,则直接进行重试。
-
MaxAutoRetries:当前节点重试次数。这里重试次数为2,那么在首次调用超时以后,会再次向同一个服务节点发起最多2次重试(总共向当前节点1+2=3次请求)。
-
MaxAutoRetriesNextServer:换N个节点重试。这里N=2,就是说在当前机器调用超时后,Feign将最多换N台机器发起调用(注意,这里将和第一个参数共同作用,也就是说,在新机器上超时后,会继续重试MaxAutoRetries+1次)。
根据上面的配置,我们可以计算一次请求的最大超时时间:
(2000 + 1000)*(2 + 1)*(2 + 1)= 27000ms