目录
1.概述
2.使用
2.1.引入
2.2.启用
2.3.切换负载均衡算法
3.负载均衡源码分析
3.1.接口
3.2.抽象类
3.3.选择服务器
3.4.原子性
4.自定义负载均衡算法
Ribbon是Netflix开源的一个客户端负载均衡库,也是Spring Cloud Netflix项目的核心组件之一。它主要用于在微服务架构中对服务进行负载均衡,以提高系统的可用性和性能。ribbon不是通信组件,而是服务调用者和通信组件之间的中间层,主要就是用来做负载均衡,选择出适合处理本次请求的服务节点。
现在整个spring cloud体系中的通信组件其实就是封装了负载均衡组件+HTTP通信组件,HTTP通信组件没什么好说的,就是用来发起HTTP请求的,市面上的HTTP通信组件也很多,诸如spring boot自带的RestTemplate,apache的Apache HttpClient等等。值得去探究一下的是负载均衡组件,其作为负载均很组件决定了请求的分发,可以说是整个通信组件的核心。
从Spring Cloud 2020.0版本开始,Spring Cloud官方已经将Ribbon标记为过时(deprecated),推荐使用Spring Cloud LoadBalancer作为替代方案。这样做是基于标准化的考虑,整个spring生态历来都是希望“包容万物”,所以社区自然不希望在负载均衡组件上直接就采用一个固定的实现,而是希望能让三方的解决方案可以平滑的接入、切换。
虽然ribbon以后不再是负载均衡组件的首选,但是作为最经典的负载均衡组件,其底层的一些思想仍然被后面的方案沿用,其实看明白ribbon的源码基本上也就明白负载均衡的原理了,万变不离其宗。
博主的上篇文章详细介绍了怎么使用eureka+通信组件来完成服务的注册、调用,其中也详细的介绍了ribbon的使用,可以移步:
详解Eureka服务注册和调用__BugMan的博客-CSDN博客
总的来说就是:
eureka集成了ribbon,导入eureka后不用单独导入ribbon,但是要注意的是一定要选对版本号,不要选到一个已经把ribbon移除的高版本,本文使用的依赖版本:
当然你也可以直接降级整个spring cloud的版本号,关于spring cloud版本号的问题,可以移步博主的另一篇文章,会有详细讲解:
详解Spring Cloud版本问题__BugMan的博客-CSDN博客
引入依赖后在承载HTTP通信组件上开启负载均衡即可,此处以spring boot自带的RestTemplate为例,这样在每次客户端(消费者)发起http请求的时候都会在本端进行负载均衡运算后再进行服务访问。
负载均衡的核心接口Irule有多个实现类,每个实现类实现不同的负载均衡算法,
常用的有,轮询、随机、可获得、重试等几种:
RoundRobinRule,轮询
RandomRule,随机
AvailabilityFilteringRule,会过滤掉,跳闸,访问故障的服务,对剩下的进行轮询
RetryRule,会按照轮询获取服务,如果服务获取失败,则会在指定的时间内进行重试
需要切换的时候直接在@Configuration中注入@Bean即可:
IRule接口:
get/setLoadBalancer:获取、修改均衡器
ILoadBalancer:
与服务器打交道,负责寻找、登记服务器
AbstractLoadBalancer实现了Irule接口,重写了均衡器的get/set方法,只留下一个抽象方法——choose,待子类重写。
所有实现类都继承抽象类AbstractLoadBalancer。各自去重写choose方法,即各自实现不同的负载均衡规则(此处以ribbon默认的负载均衡规则,RoundRobinRule为例):
choose方法即负载均衡策略,是各负载均衡类的核心方法(此处以ribbon默认的负载均衡规则,RoundRobinRule为例):
均衡器会和注册中心交互,然后记录下当前整个系统中所有服务器的相关信息,包含服务器总数,可用总数等。
向均衡器所要服务器总数、服务器可用总数,然后根据这两个值进行运算,挑选出承载该此流量的服务器。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
List reachableServers = lb.getReachableServers();
List allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
整个过程中封装服务器相关信息的Server类,其中有服务器的详细参数:
挑选过程中,为了保证原子性,使用了自旋锁(CAS),保证每次处理的只有一个访问线程,其余线程处于自选等待状态:
compareAndSet(期望值,修改值)
期望值,即版本号。
修改值,即要将版本号更新为的值。
判断期望值是否改变(前后是否相同),如果期望值未改变,则将期望值更新为修改值。
返回true,否则返回false。
存在顶级接口并且可以切换负载均衡算法,那自然可以自定义负载均衡算法,以下是一个根据服务器权重进行负载均衡的一个负载均衡算法:
public class CustomWeightedRandomRule extends AbstractLoadBalancerRule {
private AtomicInteger totalWeight = new AtomicInteger(0);
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return null;
}
List allServers = lb.getAllServers();
int serverCount = allServers.size();
if (serverCount == 0) {
return null;
}
int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight.get());
int currentWeight = 0;
for (Server server : allServers) {
currentWeight += getWeight(server);
if (randomWeight < currentWeight) {
return server;
}
}
// Fallback to the default server if no server is selected
return super.choose(key);
}
private int getWeight(Server server) {
// Return the weight of the server (custom logic)
// Example: return server.getMetadata().getWeight();
return 1; // Default weight is 1
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
super.initWithNiwsConfig(clientConfig);
// Calculate the total weight of all servers
List allServers = getLoadBalancer().getAllServers();
totalWeight.set(allServers.stream().mapToInt(this::getWeight).sum());
}
}