5. Ribbon负载均衡
1. Ribbon简介
Spring Cloud Ribbon是Netflix发布的开源项目,主要功能是提供客户端
的软件负载均衡
算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单地说,就是在配置文件中列出Load Balancer(简称LB)后面所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法
由官网https://github.com/Netflix/ribbon可知,Ribbon也进入维护中了,但是生产中大量使用中,还是比较优秀,不是立刻退出技术舞台的时刻。
Tip:未来可替换方案趋势:Spring 的LoadBalancer Ribbon=负载均衡+RestTemplate
2. Ribbon和Nginx负载均衡区别
负载均衡
:简单地说就是将用户的请求平均分摊到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡软件有Nginx,LVS,硬件F5等。
2.1 Ribbon和Nginx负载均衡区别:
Nginx是服务器负载均衡
,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的。Ribbon本地负载均衡
,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
2.2 Ribbon负载均衡
进程内LB
:将LB逻辑集成到消费方
,消费方从服务注册中心获知有哪些地址可用,然后再从这些地址中选择出一台合适的服务器。它只是一个类库,集成于消费方进程。集中式LB
:即在服务的消费方和提供方之间使用独立的LB设施
(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务提供方。
eg:我们的order调用payment工程,ribbon应该在我们的order服务消费方
3. RestTemplate
RestTemplate的一些方法 : 获取数据结果可通过 RestTemplate 的getForObject
、getForEntity
发送数据postForObject
,getForEntity 中可以获取返回的状态码、请求头等信息,通过 getBody 获取响应的内容。其余的和 getForObject 一样,也是有 3 个重载的实现:
@RestController
@RequestMapping("/consumer")
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://CLOULD-PAYMENT";
@Resource
private RestTemplate restTemplate;
@PostMapping("/order")
public CommonResult<Payment> createOrder(@RequestBody Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment", payment, CommonResult.class);
}
@GetMapping("/order/{id}")
public CommonResult<Payment> getOrderPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/" + id, CommonResult.class);
}
@GetMapping("/order/getForEntity/{id}")
public CommonResult<Payment> getOrderPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult<>(444, "操作失败");
}
}
}
4. Ribbon核心组件IRule
Ribbon在工作时分为两步: 第一步:先选择 EurekaServer,它优先选择在同一个区域内负载较少的Server; 第二步:再根据用户指定的策略,在从Server取到的服务注册列表中选择一个地址; 其中Ribbon提供了多种策略,比如轮询、随机、根据响应时间加权。
4.1 Ribbon与Eureka
spring-cloud-starter-netflix-eureka-client
3.0.x版本以前是包含对ribbon的依赖的,官方内置了随机和轮询负载均衡策略,所以不用额外引入ribbon依赖也可以使用,2020.0版本后移除了ribbon依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>不同版本</version>
</dependency>
eg:spring-cloud-starter-netflix-eureka-client2.0.x版本是包含对ribbon的依赖的,不需特殊引入

但是Spring Cloud在2020.0版本后,如3.1.3版本就移除了Ribbon,用的是 loadbalancer:

由于我们之前搭建的工程都是基于最新版本的springcloud 2021.0.3版本,导入Eureka依赖spring-cloud-starter-netflix-eureka-client后检查不到Ribbon的依赖,无法查看IRule接口代码,是使用LoadBalancer代替的Ribbon。
所以我们新搭建一个springcloud001版本以下的工程学习Ribbon,注意引入的依赖: 父pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
子pom:
<!--Eureka的依赖版本不要填,因为dependencyManagement中指定了版本,交给maven自动匹配就好-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
IRule接口在ribbon-loadbalancer包中,下载源码,可以查看IRule类图
4.2 Ribbon核心组件IRule
IRule 是一个接口。其作用是:根据特定算法从服务列表中选取一个要访问的服务,Ribbon默认的算法为轮询算法
。
IRule类图:
Ribbon中的7种负载均衡算法:
- com.netflix.loadbalancer.RoundRobinRule:轮询(默认)
- com.netflix.loadbalancer.RandomRule:随机
- com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试
- WeightedResponseTimeRule :对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule :先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
4.3 7种负载均衡算法如何切换?
前提:自定义配置类不能放在@ComponentScan所扫描的当前包及子包下,否则,我们的自定义配置配就会被所有的Ribbon客户端所共享,达不到特殊定制化目的。
1)首先定义自己的规则类(注意包的层级,不能放在@ComponentScan所扫描的当前包及子包下,也就是与启动类所在包区分开)
前边有提到Ribbon负载均衡是进程内LB,将LB逻辑集成到消费方,所以在order工程里定义规则类

2)主启动类配置上自己的规则
@RibbonClient(name="CLOUD-PROVIDER-PAYMENT",configuration = MySelfRule.class)
测试:http://localhost/consumer/order/1 发现8001、8002随机访问我们的payment 工程的,达到自定义的RandomRule规则效果。
5. 默认负载均衡算法原理
负载均衡算法:rest接口第几次请求数%服务器集群总数=实际调用服务器下标,每次重启服务后rest接口技术从1开始
eg:本例中 8001+8002组合为2台的payment集群,up(2)可看到健康的可达的机器为2. List[0] instances=120.0.0.1:8081 List[1] instances=120.0.0.1:8082 比如第一次请求进来,1%2=1,返回去下标是1,第二次请求,2%2=0,所以8001,8002来回切换,即默认的轮询算法的原理
。
6. RoundRobinRule轮询算法源码分析
IRule.java RoundRobinRule.java
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
//实现接口的方法choose,选择哪一个负载均衡算法
public Server choose(ILoadBalancer lb, Object key) {
//没有负载均衡算法,报错
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
//选择可达的健康的机器(活着的)
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
//upCount健康机器数
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
//nextServerIndex 计算返回的机器下标,用到了CAS自旋锁知识
//(上面提到的机器总数2台,轮询返回下标0、1)
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
//计算返回的机器下标,用到了CAS自旋锁
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
小结:RoundRobinRule轮询算法用到了CAS自旋锁知识