此文章很大部分转载https://www.mrhelloworld.com/,博主均测试通过,博主均测试通过
什么是 Ribbon
Ribbon 是一个基于 HTTP 和 TCP 的 客服端负载均衡工具,它是基于 Netflix Ribbon 实现的。
它不像 Spring Cloud 服务注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个 Spring Cloud 微服务中。包括 Feign 提供的声明式服务调用也是基于该 Ribbon 实现的。
Ribbon 默认提供很多种负载均衡算法,例如轮询、随机等等。甚至包含自定义的负载均衡算法。
Ribbon 解决了什么问题
Ribbon 提供了一套微服务的负载均衡解决方案。
负载均衡不同方案的区别
目前业界主流的负载均衡方案可分成两类:
- 集中式负载均衡(服务器负载均衡),即在 consumer 和 provider 之间使用独立的负载均衡设施(可以是硬件,如 F5,也可以是软件,如 nginx),由该设施负责把访问请求通过某种策略转发至 provider;
- 进程内负载均衡(客户端负载均衡),将负载均衡逻辑集成到 consumer,consumer 从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。Ribbon 属于后者,它只是一个类库,集成于 consumer 进程,consumer 通过它来获取 provider 的地址。
集中式负载均衡
进程内负载均衡
Ribbon 负载均衡策略
轮询策略(默认)
策略对应类名:RoundRobinRule
实现原理:轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个 provider,第 1 次取第 1 个,第 2 次取第 2 个,第 3 次取第 3 个,以此类推。
权重轮询策略
策略对应类名:WeightedResponseTimeRule
实现原理:
- 根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。
- 原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider 附上一个权重,并按权重随机选择 provider,高权越重的 provider 会被高概率选中。
随机策略
策略对应类名:RandomRule
实现原理:从 provider 列表中随机选择一个。
最少并发数策略
策略对应类名:BestAvailableRule
实现原理:选择正在请求中的并发数最小的 provider,除非这个 provider 在熔断中。
重试策略
策略对应类名:RetryRule
实现原理:其实就是轮询策略的增强版,轮询策略服务不可用时不做处理,重试策略服务不可用时会重新尝试集群中的其他节点。
可用性敏感策略
策略对应类名:AvailabilityFilteringRule
实现原理:过滤性能差的 provider
- 第一种:过滤掉在 Eureka 中处于一直连接失败的 provider。
- 第二种:过滤掉高并发(繁忙)的 provider。
区域敏感性策略
策略对应类名:ZoneAvoidanceRule
实现原理:
- 以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的 provider。
- 如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权 重。
Ribbon 入门案例
eureka-demo
聚合工程。SpringBoot 2.2.4.RELEASE
、Spring Cloud Hoxton.SR1
。
Ribbon 中对于集群的服务采用的负载均衡策略默认是轮询。
创建项目
使用学习 Eureka 时的 eureka-demo
项目,在该项目中创建子项目 service-provider02
添加依赖
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"> <modelVersion>4.0.0modelVersion> <groupId>com.examplegroupId> <artifactId>service-provider02artifactId> <version>1.0-SNAPSHOTversion> <parent> <groupId>com.examplegroupId> <artifactId>eureka-demoartifactId> <version>1.0-SNAPSHOTversion> parent> <dependencies> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <scope>providedscope> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-actuatorartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions> dependency> dependencies> project>
配置文件application.yml
server:
port: 7071 # 端口
spring:
application:
name: service-provider # 应用名称(集群下相同)
# 配置 Eureka Server 注册中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 设置服务注册中心地址
defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/
# 度量指标监控与健康检查
management:
endpoints:
web:
exposure:
include: shutdown # 开启 shutdown 端点访问
endpoint:
shutdown:
enabled: true # 开启 shutdown 实现优雅停服
服务及启动类
将所有代码复制粘贴一份至 server-provider02,修改启动类名称即可.为了更直观的看到负载均衡的效果,我们在 service-consumer
项目中将服务地址打印至控制台。
注册中心
运行完整的 Eureka 环境,访问:http://localhost:8761/ 可以看到现在已经有两个服务提供者。
消费服务
多次访问:http://localhost:9090/order/1 可以看到默认使用的是轮询策略。
Ribbon 负载均衡策略设置
这里是ribbon负载均衡默认的实现, 由于是笔记的关系,这里不好测试,只能你们自己去测试一下了, 具体怎么 使用呢?
全局
在启动类或配置类中注入负载均衡策略对象。所有服务请求均使用该策略。
@Bean
public IRule iRule(){
return new RoundRobinRule();
}
多次访问:http://localhost:9090/order/1 结果如下:
局部
修改配置文件指定服务的负载均衡策略。格式:服务应用名.ribbon.NFLoadBalancerRuleClassName
# 负载均衡策略 # service-provider 为调用的服务的名称 service-provider: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
自定义负载均衡策略:
我们刚刚讲过,只要实现了IRule就可以完成自定义负载均衡,至于具体怎么来,我们先看看他默认的实现
/* * * Copyright 2013 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.netflix.loadbalancer; import com.netflix.client.config.IClientConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * The most well known and basic load balancing strategy, i.e. Round Robin Rule. * * @author stonse * @author Nikos Michalakis* */ 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); } 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) { ListreachableServers = lb.getReachableServers(); List allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if ((upCount == 0) || (serverCount == 0)) { log.warn("No up servers available from load balancer: " + lb); return null; } 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. */ 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) { } }
我们来看看这个类AbstractLoadBalancerRule
/** * Class that provides a default implementation for setting and getting load balancer * @author stonse * */ public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware { private ILoadBalancer lb; @Override public void setLoadBalancer(ILoadBalancer lb){ this.lb = lb; } @Override public ILoadBalancer getLoadBalancer(){ return lb; } }
这里我们能发现,还是我们上面所说过的 实现了IRule就能够自定义负载均衡即使是他默认的策略也实现了IRule 我们可以直接把代码copy过来改动一点
import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.List; import java.util.Random; public class LubanRule extends AbstractLoadBalancerRule { //原来是纯随机策略 我们现在改为。 如果一个下标已经被随机到了2次了,第三次还是同样的下标的话,那就再随机一次 Random rand; private int lastIndex = -1; private int nowIndex = -1; private int skipIndex = -1; public LubanRule() { rand = new Random(); } /** * Randomly choose from all living servers */ public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } ListupList = lb.getReachableServers(); List allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { /* * No servers. End regardless of pass, because subsequent passes * only get more restrictive. */ return null; } int index = rand.nextInt(serverCount); System.out.println("当前下标为:"+index); if (skipIndex>=0&&index == skipIndex) { System.out.println("跳过"); index = rand.nextInt(serverCount); System.out.println("跳过后的下标:"+index); } skipIndex=-1; nowIndex = index; if (nowIndex == lastIndex) { System.out.println("下一次需要跳过的下标"+nowIndex); skipIndex = nowIndex; } lastIndex = nowIndex; server = upList.get(index); if (server == null) { /* * The only time this should happen is if the server list were * somehow trimmed. This is a transient condition. Retry after * yielding. */ Thread.yield(); continue; } if (server.isAlive()) { return (server); } // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } }
这里我们就把自己写的Rule给new出来交给spring 就好了
@Bean public IRule iRule(){ return new LubanRule(); }
具体测试的话就不测试了, 那个效果放在笔记上不太明显,可以自己把代码copy过去测试一下
Ribbon 点对点直连
点对点直连是指绕过注册中心,直接连接服务提供者获取服务,一般在测试阶段使用比较多。
添加依赖
在调用方 pom 文件中引入 ribbon
依赖,需要注意的是如果 pom 中有 Eureka 的依赖,则需要去除 Eureka 的依赖。
<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-ribbonartifactId> dependency>
配置文件
配置文件中关闭 Eureka,添加直连的服务地址。如果不设置负载均衡策略默认使用轮询策略。
# 负载均衡策略
# service-provider 为调用的服务的名称
service-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 点对点直连模式,指定具体的 Provider 服务列表,多个用逗号隔开
listOfServers: http://localhost:7070,http://localhost:7071
# 关闭 Eureka 实现 Ribbon 点对点直连
ribbon:
eureka:
enabled: false # false:关闭,true:开启
访问
关闭 Eureka 注册中心,服务提供者由于无法连接至注册中心所以会报连接异常。但是服务是可以正常可消费的,所以目前使用的是点对点的方式来进行调用的。
多次访问:http://localhost:9090/order/1 结果如下:
至此 Ribbon 负载均衡所有的知识点就讲解结束了。