首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

spring cloud负载均衡是怎么实现的

1、什么是负载均衡

负载均衡,它的含义简单理解就是当访问一个应用集群的时候,尽可能使请求均匀地落到集群中的某一台机器中,不至于使某一台机器的接收的请求过多。也即是说,因为先有了集群,所以才会有负载均衡这个概念。当访问单机的服务时,谈不上负载均衡。负载均衡,又分为客户端负载均衡和服务端负载均衡。

1.1、客户端负载均衡

客户端负载均衡指的是当客户端要调用远程服务的时候,自己决定要调用远程服务中的哪一台机器。当客户端能够获取到远程服务的所有ip和对应的port信息的时候,它就可以使用某种负载均衡算法,从这些ip中选择一个ip(也包括对应的端口号),然后对选中的ip上的服务直接发起调用。

1.2、服务端负载均衡

服务端负载均衡指的是客户端已经把请求发出来,服务端来决定将请求转发到集群中的某台机器上。我们常用的nginx就承担了这种职责,我们直接访问nginx的ip和应用的路径,nginx根据配置的应用的转发信息,将请求转发到后端的某台机器。

spring cloud官方提供了它自己的客户端负载均衡的抽象和实现,本篇我们就来看下它的实现原理。

2、前期回顾

我们在之前的文章中谈到在spring cloud中使用nacos服务注册和发现功能的时候,pom文件中引入了如下的依赖:

然后我们又写了如下的代码:

创建restTemplate实例的时候使用了@LoadBalanced注解,然后我们就可以直接使用容器中的restTemplate实例以下面的代码调用目标服务:

为什么我们这么写就能访问到目标服务呢?这里面的原理到底是啥?其实这里的restTemplate还是很普通的RestTemplate对象,只不过它比我们平时直接用@Bean注解生成的restTemplate要多了一点属性。我们先对这块的功能做下概述:

上面是一个大概的请求执行过程,一个小小的restTemplate对象之所以能拥有负载均衡和服务发现的能力,和@LoadBalanced这个注解有很大的关系。接下来我们就来分析下这里面涉及到的对象都是哪里来的。

3、原理讲解

我们pom文件中引入的spring-cloud-starter-alibaba-nacos-discovery包会自动导入spring-cloud-commons包和spring-cloud-loadbalancer包,进而又导入了一堆自动配置类。spring-cloud-loadbalancer包是spring cloud官方提供的负载均衡的实现。

其实负载均衡组件存在的意义(功能),说点大白话,就是要从一堆服务器中选一个服务器出来,然后再访问这台被选中的服务器。我们可以把这一堆服务器比作是一个服务列表提供者对象,我们能从它里面直接拿到这些服务器的信息。而我们本文用到的nacos服务发现组件,其实也算是一个服务列表提供者对象,因为我们也可以从它里面获取到nacos远程服务上存储的关于某个服务名称下面的服务器(实例)信息。因此,我们在此处结合前面的例子分析spring cloud负载均衡功能的时候,也会涉及到nacos服务发现相关的功能。负载均衡组件要操作的对象就是nacos服务发现组件从nacos服务器上读取到的指定服务名下面的所有服务实例信息。

3.1、@LoadBalanced注解

首先我们先来看下@LoadBalanced注解,它位于Spring cloud commons包,有如下的定义:

它本身很简单,@Qualifier是它的元注解,所以它本身就是被当做一个标记使用。那么谁会来解析(利用)这个标记呢?就是下面的LoadBalancerAutoConfiguration配置类。

3.2、LoadBalancerAutoConfiguration配置类

LoadBalancerAutoConfiguration位于spring-cloud-commons包,它有如下的定义:

我们来对这个配置类做下拆解。

3.2.1、配置类的restTemplates字段

这个配置类有这个字段声明:

它的意思就是注入spring容器中所有标记了@LoadBalanced注解的restTemplate对象,如果我们在之前的BeanConfig配置类增加如下代码:

那这个restTemplate1对象是不会被注入到restTemplates列表中的,restTemplates列表中依然只有一个对象。这就是@LoadBalanced注解的作用,它只是被用作一个标记,来标记哪些对象可以为我所用。关于@Qualifier注解相关的具体解析,大家感兴趣的话可以先查看spring源码中ContextAnnotationAutowireCandidateResolver类的父类QualifierAnnotationAutowireCandidateResolver中的内容。

3.2.2、配置类的内部类LoadBalancerInterceptorConfig

LoadBalancerInterceptorConfig是LoadBalancerAutoConfiguration的内部类,同时它也是一个配置类。从它的名字我们可以看出,它是一个负载均衡拦截器配置类。该配置类有如下的作用:

其实我们的restTemplate对象并没用到代理的技术,它就是比我们平常使用的restTemplate对象多了一个负载均衡的拦截器。那这个负载均衡拦截器是啥时候被设置到restTemplate对象中的呢?

3.2.3、SmartInitializingSingleton实例对象

LoadBalancerAutoConfiguration配置类还定义了一个bean对象,名字叫loadBalancedRestTemplateInitializerDeprecated,它是SmartInitializingSingleton类型的。那SmartInitializingSingleton又是个啥呢?

我们来看下它的定义:

它内部只有一个抽象方法,SmartInitializingSingleton类型的bean对象会在spring初始化初始化bean的阶段结束时被调用。

spring容器启动的时候会调用AbstractApplicationContext的refresh方法,该方法内部有如下代码:

而finishBeanFactoryInitialization方法内部又会执行如下代码:

此处的preInstantiateSingletons方法会确保所有的非懒加载的bean对象都会被实例化。而preInstantiateSingletons方法内部在初始化所有的非懒加载的bean之后就会执行如下代码:

上面这段代码的意思就是:循环spring容器中的bean对象,如果当前bean对象是SmartInitializingSingleton类型的,就执行它的afterSingletonsInstantiated方法。

那么到这里我们已经知道SmartInitializingSingleton被调用的地方了,咱们再回到LoadBalancerAutoConfiguration配置类。它是这么创建SmartInitializingSingleton对象的:

它内部会针对每个标记了@LoadBalanced注解的restTemplate对象挨个调用spring容器中的RestTemplateCustomizer对象的customize方法。我们在3.2.2部分提到的RestTemplateCustomizer对象,就是在此处执行的。也就是说,当SmartInitializingSingleton实例对象的afterSingletonsInstantiated方法被调用后,我们的restTemplate对象的拦截器列表中就多了一个LoadBalancerInterceptor对象。

3.3、spring-cloud-loadbalancer包的配置类

spring-cloud-loadbalancer包是spring cloud官方提供的客户端负载均衡的实现,我们需要重点关注下这个包的自动配置类。

3.3.1、BlockingLoadBalancerClientAutoConfiguration配置类

这个配置类定义了LoadBalancerClient类型的bean信息,真实类型是BlockingLoadBalancerClient,后面负载均衡拦截器执行的时候会用到它。

3.3.2、LoadBalancerClientConfiguration配置类

这个配置类自动配置了如下的两个bean,RoundRobinLoadBalancer和ServiceInstanceListSupplier:

3.4、spring-cloud-starter-alibaba-nacos-discovery包的配置类

nacos服务发现的自动配置包主要就是用来提供一些服务发现的组件对象,并将其注册到spring容器中,供前面的loadbalancer包中的组件对象使用。

3.4.1、NacosLoadBalancerClientConfiguration配置类

NacosLoadBalancerClientConfiguration位于包中,它提供ServiceInstanceListSupplier类型的bean对象,简单来说,它就是一个服务列表提供者对象,而该对象的真实类型是DiscoveryClientServiceInstanceListSupplier:

3.4.2、NacosDiscoveryClientConfiguration配置类

它提供了DiscoveryClient的实例,它会和nacos服务器交互,能根据指定的服务名称去nacos服务器上查询该服务名下面所有的服务实例信息。上面的ServiceInstanceListSupplier实例内部会用到它。

3.5、RestTemplate对象的执行过程

3.5.1、RestTemplate内部方法执行过程

当我们执行下面的代码时

会调用InterceptingClientHttpRqeust的execute方法,该方法在其父类AbstractClientHttpRequest中:

我们再来看下executeInternal方法:

它内部调用了重载的executeInternal方法,而这个重载的方法是在InterceptingClientHttpRequest中实现的:

这里重点就来了,InterceptingClientHttpRequest的内部类InterceptingRequestExecution的execute方法被执行:

上面的代码就调用了我们先前提到的负载均衡过滤器LoadBalancerInterceptor,剩下的代码就交给了LoadBalancerInterceptor,由LoadBalancerInterceptor执行并将结果返回给RestTemplate。

3.5.2、LoadBalancerInterceptor执行过程

我们接着看intercept方法:

里面的loadBalancer对象就是我们在3.3部分提到的BlockingLoadBalancerClient对象。

3.5.3、BlockingLoadBalancerClient执行过程

我们继续看它的execute方法。

里面很重要的一行代码就是:

它会根据服务id(名称)选择一个可用的服务实例,供后续直接调用执行。

choose方法内部的loadBalancer对象其实就是3.3.2部分看到的RoundRobinLoadBalancer对象。

3.5.4、RoundRobinLoadBalancer对象的choose方法

方法里面的supplier对象就是我们在3.4.1部分提到的服务列表提供者DiscoveryClientServiceInstanceListSupplier对象。它内部就会调用nacos的NacosDiscoveryClient对象,来从nacos服务器获取指定服务名称下面的服务实例信息。

4、小结

本篇从restTemplate对象入手,简单分析了restTemplate如何借助spring cloud官方的负载均衡组件以及nacos服务发现组件的功能来完成了请求的处理过程。但是有一点需要注意的是:如果我们传给restTemplate对象的url地址包含的是域名或者ip,那我们就不能使用这个经过负载后的restTemplate对象,而应该在配置类中创建一个普通的bean对象,上面不能加@LoadBalanced注解,比如:

经过负载后的restTemplate只能处理带服务名称的url地址,而不能处理带域名或ip的url地址,否则就会报无法解析服务名的错误。因此,当我们系统集成服务发现组件的数据,我们系统是可能会用到多个restTemplate对象。

另外,本篇我们讲的负载均衡客户端是spring cloud官方提供的loadbalancer包,其实我们可以直接把这个包排除掉,然后使用ribbon负载均衡组件。这个替换过程很简单,只需要修改maven依赖,而不必更改我们的系统代码。之所以如此方便,也要得益于spring cloud commons包提供的各种抽象接口以及以及默认实现组件,以后我们会找时间再来聊聊这块的知识~

结束语

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230614A04T7900?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券