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包提供的各种抽象接口以及以及默认实现组件,以后我们会找时间再来聊聊这块的知识~
结束语
领取专属 10元无门槛券
私享最新 技术干货