SpringCloud负载均衡源码解析 | 带你从表层一步步剖析Ribbon组件如何实现负载均衡功能

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringCloud负载均衡源码解析 | 带你从表层一步步剖析Ribbon组件如何实现负载均衡功能

添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?

1、负载均衡原理

SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。

思考:发出的请求明明是http://userservice/user/2,怎么变成了http://localhost:8081/user/2的呢?

2、源码分析

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然是因为有什么组件帮我们根据service名称,获取到了服务实例的ip和端口。

先说结论:它就是LoadBalancerInterceptor

这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

下面进行源码分析。

2.1、@LoadBalanced

按住CTRL键,点进@LoadBalanced源码,选择加载完整,可以看到如下:

看起来非常的平平无奇,但是请留意上面的英文说明:

Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient.

简单来说,就是这个@LoadBalanced注解是用来标记的,标记要使用的是LoadBalancerClient。

所以真正起作用的其实是LoadBalancerClient这个Java类。

下面找到LoadBalancerClient这个类。

2.2、LoadBalancerClient

可以看到,这个类也是一个接口。

看一下类里面有什么方法:

只有这三个方法,三个都打断点尝试一下,看看拦截的是哪一个。

经过尝试,拦截的是:

<T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException;

并且,拦截到实现类是RibbonLoadBalancerClient

但是这时候,我再往下调试断点,发现有点没有头绪了

后面的内容并没有像前文这样显而易见的内容,都是一些判断之类的业务逻辑。

此时想起Ribbon组件,应该就是一整套东西。

那么查一下是否还有别的类?

让我们接着往下看

2.3、RibbonAutoConfiguration

查看ribbon包下其他的类,发现以ribbon开头的Java文件如下:

其中有一个很引人注目的类,即是配置类RibbonAutoConfiguration

点进去看一下:

英文提示信息是:Auto configuration for Ribbon (client side load balancing).

顾名思义,这个类果然是自动配置ribbon相关内容的。

看一下这个类实现了什么方法:

此时看见了LoadBalancerClient()方法,那么这个LoadBalancerClient()LoadBalancerClient.java会不会有什么关系呢?点过去看一下:

原来这个方法是返回了一个新的RibbonLoadBalancerClient对象,并且限制了注入LoadBalancerClient对象只允许注册一个实例。

其实,返回了一个新的RibbonLoadBalancerClient对象,本质上也是返回了一个LoadBalancerClient对象,因为RibbonLoadBalancerClient是实现LoadBalancerClient的:

2.4、LoadBalancerAutoConfiguration

进入LoadBalancerClient,查看哪些类实现了这个接口:

又出现一个自动配置类,点进去查看:

英文提示:Auto-configuration for blocking client-side load balancing.

顾名思义,自动配置阻塞客户端负载平衡。

看来,关键点找到了!

看一下这个类实现了什么方法:

可以看到这里有一个LoadBalancerInterceptorConfig拦截器配置。

那么猜想里面应该是配置了拦截器

这符合之前分析的流程,Ribbon组件接收到请求,然后去找Eureka拉去实例列表,那这个过程的实现,不就是需要一个拦截器么?请接着往下看:

原来这是一个静态内部类。这个内部类的内容如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
    @Bean
    public LoadBalancerInterceptor loadBalancerInterceptor(
            LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(
            final LoadBalancerInterceptor loadBalancerInterceptor) {
        return restTemplate -> {
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                    restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        };
    }
}

这部分内容,我们让通义灵码解释一下:

由此,重点就在LoadBalancerIntercepor了。

2.5、LoadBalancerIntercepor

进入LoadBalancerIntercepor

LoadBalancerIntercepor实现了ClientHttpRequestInterceptor(翻译过来是:客户端Http请求拦截器)。

到这里,基本上就没错了,接下来就是打断点接着拦截!

实现的方法如下:

intercept方法打断点:

可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:

request.getURI():获取请求uri,本例中就是 http://userservice:8081/user/2

originalUri.getHost():获取uri路径的主机名,其实就是服务id,userservice

this.loadBalancer.execute():处理服务id,和用户请求。

这里的this.loadBalancerLoadBalancerClient类型:

继续跟入末尾的execute方法。

2.6、再回LoadBalancerClient

可以注意到文章目录其实有两个LoadBalancerClient

第一次是断点定位到execute方法之后没有头绪,

第二次则是带着答案的合理猜测来的。

继续跟入this.loadBalancer.execute方法:

此时来到了RibbonLoadBalancerClient.java。下面进入这个类。

2.7、RibbonLoadBalancerClient

继续进入RibbonLoadBalancerClient的execute方法:

(进入发现其实还是RibbonLoadBalancerClient类里面的方法,只不过是别的execute方法):

2.7.1、DynamicServerListLoadBalancer

注意上图中的DynamicServerListLoadBalancer,在这里已经拉取到实例了。

上图是DynamicServerListLoadBalancer类的说明,意思翻译过来就是:

具有使用动态源获取候选服务器列表的能力的LoadBalancer。例如,服务器列表可能会在运行时更改。它还包含一些工具,其中服务器列表可以通过筛选条件传递,以过滤掉不符合所需条件的服务器。

2.7.2、getServer

注意到这里有一个getServer方法,点进去一探究竟:

再进chooseServer方法:

来到了ZoneAwareLoadBalancer.javachooseServer方法

再进super.chooseServer(key),来到了ZoneAwareLoadBalancer的父类BaseLoadBalancer

2.8、BaseLoadBalancer

进入BaseLoadBalancerchooseServer方法:

断点来到了rule.choose(key)

rule则是规则的意思,查看一下rule是什么:

根据命名规范,I是指interface接口的意思,所以IRule是一个接口:

英文提示信息:

Interface that defines a "Rule" for a LoadBalancer.

A Rule can be thought of as a Strategy for loadbalacing.

Well known loadbalancing strategies include Round Robin, Response Time based etc.

信息主要内容:规则被认为是一种负载平衡策略。众所周知的负载平衡策略包括轮询、基于响应时间等。

所以到这一步,就已经很明显了。

SpringCloud底层利用Ribbon实现负载均衡,而Ribbon则通过IRule实现负载均衡。

也就是说,Ribbon实现负载均衡的组件,一定是IRule的实现类

在IDEA中CTRL+H查看一下IRule的实现类:

RoundRobinRule.java就是实现了轮询规则。

2.9、RoundRobinRule

进入RoundRobinRule,查看实现方法:

打上断点,看看Ribbon组件有没有使用RoundRobinRule,有则说明Ribbon的原理就是轮询策略实现负载均衡。

我们的验证是没有错的,Ribbon组件实现负载均衡就是轮询规则。

2.10、总结

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:

基本流程如下:

  • 拦截RestTemplate请求http://userservice/user/2
  • RibbonLoadBalancerClient会从请求url中获取服务名称,也就是userservice
  • DynamicServerListLoadBalancer根据userservice到eureka拉取服务列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用内置负载均衡的轮询规则,从列表中选择一个,例如localhost:8081
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/2,发起真实请求

3、负载均衡策略

3.1、负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:

不同规则的含义如下:

内置负载均衡规则类

规则描述

RoundRobinRule

简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。

AvailabilityFilteringRule

对以下两种服务器进行忽略:  

(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。  

(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。

WeightedResponseTimeRule

为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。

ZoneAvoidanceRule

以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。

BestAvailableRule

忽略那些短路的服务器,并选择并发数较低的服务器。

RandomRule

随机选择一个可用的服务器。

RetryRule

重试机制的选择逻辑

默认的实现就是ZoneAvoidanceRule,是一种轮询方案

3.2、自定义负载均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
    return new RandomRule();
}
  1. 配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则

注意,一般用默认的负载均衡规则,不做修改。

3.3、饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true
    clients: userservice
相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
11天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
16天前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
39 5
|
23天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
40 3
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
56 5
|
2月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
|
3月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
129 1
|
1月前
|
JSON SpringCloudAlibaba Java
Springcloud Alibaba + jdk17+nacos 项目实践
本文基于 `Springcloud Alibaba + JDK17 + Nacos2.x` 介绍了一个微服务项目的搭建过程,包括项目依赖、配置文件、开发实践中的新特性(如文本块、NPE增强、模式匹配)以及常见的问题和解决方案。通过本文,读者可以了解如何高效地搭建和开发微服务项目,并解决一些常见的开发难题。项目代码已上传至 Gitee,欢迎交流学习。
143 1
Springcloud Alibaba + jdk17+nacos 项目实践
|
1月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
2月前
|
人工智能 前端开发 Java
Spring Cloud Alibaba AI,阿里AI这不得玩一下
🏀闪亮主角: 大家好,我是JavaDog程序狗。今天分享Spring Cloud Alibaba AI,基于Spring AI并提供阿里云通义大模型的Java AI应用。本狗用SpringBoot+uniapp+uview2对接Spring Cloud Alibaba AI,带你打造聊天小AI。 📘故事背景: 🎁获取源码: 关注公众号“JavaDog程序狗”,发送“alibaba-ai”即可获取源码。 🎯主要目标:
97 0
下一篇
无影云桌面