![深入理解Spring Cloud与实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/869/41202869/b_41202869.jpg)
3.2 Spring Cloud LoadBalancer负载均衡组件
SCL作为新一代Spring Cloud客户端负载均衡的实现,若要使用,需要在pom里加上依赖:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_79_1.jpg?sign=1739031645-pgh3hNOWQK4sAFIEwxrvUSPztBsP58jP-0-33c607319d18b75e5cb2d42e7a8484e9)
SCL相关的代码在spring-cloud-commons模块中,相关类和接口的关系如图3-1所示。
图3-1中,这些类和定义的含义如下:
·ServiceInstanceChooser:服务实例选择器,根据服务名获取一个服务实例(ServiceInstance)。
·LoadBalancerClient:客户端负载均衡器,继承ServiceInstanceChooser,会根据ServiceInstance和Request请求信息执行最终的结果。
·BlockingLoadBalancerClient:基于Spring Cloud LoadBalancer的LoadBalancerClient默认实现。
·RibbonLoadBalancerClient:基于Netflix Ribbon的LoadBalancerClient实现。
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_80_1.jpg?sign=1739031645-RaW6Jn40hmMqFVeciOzKBQ8yarEOCel1-0-4c0ffecf6ed24971176cc090cf22bf3b)
图3-1
1.ServicelnstanceChooser
org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser 服务实例选择器的定义如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_80_2.jpg?sign=1739031645-YPNzhJLPkOUgWE6dGbWzlCGT9xgkKGIS-0-2e71310e6864b44f93460b20d0c2bdd3)
我们自定义一个 ServiceInstanceChooser 的实现类来完成负载均衡操作(由于客户端负载均衡器 LoadBalancerClient 跟 Request 请求信息有着强耦合的关系,会涉及 RestTemplate 或OpenFeign的一些概念,相关内容将在后面介绍):
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_80_3.jpg?sign=1739031645-W3NvnSgIALaNztyyn9p3GPv36ZXcdtbe-0-7e3b339f2bf0261ac87d84b0d34d0f30)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_81_1.jpg?sign=1739031645-j9tR2BTZOLkJSrC2dHpEvjyHzMYv6PxY-0-ef7fe50b13cd3a633e40e4bbbb43a292)
接下来在 Controller 里使用 RandomServiceInstanceChooser (内部使用随机算法)获取ServiceInstance,再使用RestTemplate进行服务调用:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_81_2.jpg?sign=1739031645-jUIiBdkprE677L1G3Thnjjy0Het9BLFZ-0-04bf0ec1d752f45969bd60e98119d140)
执行Shell脚本,调用Controller的customChooser路径,返回结果如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_81_3.jpg?sign=1739031645-s1oVanbe998lLdIM8SUk6wywdv1RCsYR-0-fe1440987e28c4cf3873caa4a97d9643)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_82_1.jpg?sign=1739031645-6syLkerVIPlR5C7nRTHNdFc4MkFhVF67-0-3e3d8280a515194b4925503740e39730)
我们可以看到,RandomServiceInstanceChooser和 Controller里的 customChooser方法这两段代码内部先使用 DiscoveryClient基于服务名获取这个服务对应的所有 ServiceInstance集合,然后根据负载均衡算法从这个集合中得到一个 ServiceInstance,最后基于获取的 ServiceInstance里的 IP和端口使用 RestTemplate发起 HTTP调用。仔细想想,这两段代码还有更进一步的简化空间:直接使用RestTemplate根据服务名进行调用,屏蔽ServiceInstance的获取细节。
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_82_2.jpg?sign=1739031645-R2fwhwBLGB4CZQ0aXGqEQF2n9xDj3O9q-0-4084d6294b6fb14c770011f523ee5e3e)
在实际情况下,使用 Spring Cloud 确实可以直接基于服务名进行服务调用。这是因为Spring Cloud扩展了RestTemplate,只需要在定义 RestTemplate Bean时加上@LoadBalanced 注解,就可以基于服务名进行服务调用:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_82_3.jpg?sign=1739031645-qX3TboiKocBzKO7CHGhTlVXcnBldBt1t-0-cd7d158a6fa5d964feabcb644b8a7eb6)
这个神秘的@LoadBalanced注解在底层做了什么事情呢?我们来分析一下。
2.@LoadBalanced
spring-cloud-commons 模块中的 META-INF/spring.factories 文件里存在 LoadBalancerAuto-Configuration 这个自动化配置类,根据工厂加载机制会被 ApplicationContext 加载。该自动化配置类内部的Bean构造代码如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_82_4.jpg?sign=1739031645-W2PSWgfnuTritlp03g5wDEIunwPDosuZ-0-fc988460bf3c8b187861b6749305cead)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_83_1.jpg?sign=1739031645-B74vUUDEmMAEQLqHIGJgX4GExmZEvP3Q-0-e085fe664f12f5bfdbacf72cc50d6d0d)
上述代码中:
①获取ApplicationContext中所有被@LoadBalanced注解修饰的RestTemplate。
② List<RestTemplateCustomizer>是 ApplicationContext 存在的 RestTemplateCustomizer Bean的集合。
③ 遍历代码①处得到的 RestTemplate 集合,并使用 RestTemplateCustomizer 集合给每个RestTemplate定制。
这个 RestTemplateCustomizer 定制的时候做了哪些操作呢?LoadBalancerAutoConfiguration内部的LoadBalancerInterceptorConfig配置类中定义了RestTemplateCustomizer:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_83_2.jpg?sign=1739031645-wNLcxen8uWGgmd9uNZoV3ruWNZeyA9l5-0-a6057523c894d3378d5986da5c4ef243)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_84_1.jpg?sign=1739031645-hLm7swkl1q4cx1eN6AzuWy5BFKJYosr3-0-ee9517502eb7d67a52310452f5d40e2f)
上述代码中:
① 条件注解。LoadBalancerInterceptorConfig配置类只有在ClassLoader不存在 RetryTemplate (Spring Retry框架提供的模板类)时才会生效。
②定义LoadBalancerInterceptor Bean,这个拦截器继承ClientHttpRequestInterceptor,可以被添加到RestTemplate的拦截器列表中。
③ 定义RestTemplateCustomizer Bean,会在LoadBalancerAutoConfiguration里的RestTemplate-Customizer列表中存在。
④LoadBalancerInterceptor参数是代码②处创建的Bean。
⑤ 使用 lambda表达式在 RestTemplate的拦截器列表中添加 LoadBalancerInterceptor拦截器。
提示:如果ClassLoader存在RetryTemplate,会触发另外一个配置类:RetryInterceptorAuto-Configuration。该配置类内部的操作与 LoadBalancerInterceptorConfig配置类唯一的区别就是构造了 RetryLoadBalancerInterceptor 拦截器(跟 LoadBalancerInterceptor 相比,在RestTemplate调用失败的情况下会进行重试操作)。
看到这里,大家应该明白了。@LoadBalanced 直接修饰的 RestTemplate 会被添加一个LoadBalancerInterceptor拦截器。接下来进入 LoadBalancerInterceptor拦截器,看它内部做了哪些操作,其代码如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_85_1.jpg?sign=1739031645-nS7M7BbzZfd5jeBfLqmZ37DEXp4ANwZK-0-4c4b7fab394eba0055b25206f949ad66)
上述代码中:
① LoadBalancerInterceptor 构造器需要 LoadBalancerClient 和 LoadBalancerRequestFactory参数(默认会在 LoadBalancerAutoConfiguration 里被构造,开发者可以进行覆盖)。前者根据负载均衡请求(LoadBalancerRequest)和服务名做真正的服务调用,后者构造负载均衡请求(LoadBalancerRequest),构造过程中会使用LoadBalancerRequestTransformer对请求做一些自定义的转换操作(默认情况下,LoadBalancerRequestTransformer接口无任何实现类,开发者可以根据业务构造Bean进行Request的转换操作)。
②服务名使用URI中的host信息。
③使用LoadBalancerClient客户端负载均衡器做真正的服务调用。
3.LoadBalancerClient
LoadBalancerClient(客户端负载均衡器)会根据负载均衡请求和服务名执行真正的负载均衡操作,在介绍SCL类和接口关系时也提到过这个接口,该接口的具体定义如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_86_1.jpg?sign=1739031645-dc6F4oYdaUEop8XKeynj4YE2Uezsg3t4-0-faa36dcc5b7fd178cb39fa5cffca9cb0)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_87_1.jpg?sign=1739031645-PV0lLN0PUQOv9GmM8Cznn4gG3CQOeN79-0-824957cf507ca7fde2e47e5c0af9b0bf)
·reconstructURI方法。这个方法用于重新构造URI。比如,要访问nacos-provider-lb服务下的“/”路径,这个URI为http://nacos-provider-lb/。nacos-provider-lb 服务在注册中心有10个服务实例,某个服务实例 ServiceInstance的IP为192.168.1.100,端口为8080。那么重新构造的真正URI为http://192.168.1.100:8080/。
·execute 方法。有两个重载方法,其中一个方法比另外一个方法多了 ServiceInstance服务实例参数。没有 ServiceInstance 参数的方法内部会通过 choose 方法(父接口ServiceInstanceChooser 提供)使用负载均衡算法得到一个 ServiceInstance,然后调用带有ServiceInstance参数的execute方法。
execute方法的源码如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_87_2.jpg?sign=1739031645-vdSVfDjjARZVedUD7WF7S2jpa7z9f1gZ-0-bf817b22e7d8ea802ecdc07fa1995e7c)
LoadBalancerRequest 表示一次负载均衡请求,会被 LoadBalancerRequestFactory 构造。构造出的负载均衡请求实现类是 ServiceRequestWrapper(内部基于服务实例和请求信息构造出真正的 URI)。然后根据 LoadBalancerRequestTransformer 做二次加工。LoadBalancerRequest 接口定义如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_88_1.jpg?sign=1739031645-22EEzHLwRz04qw3W9etA1YfDHNdNlMYp-0-bc0b77923043908f351728f43a07bc78)
LoadBalacerRequestFactory构造负载均衡请求的过程如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_88_2.jpg?sign=1739031645-6ARLG2NZTuJy3oGaOG8LdRen2wZ3RnMF-0-26fa3ee3aace443a3fb0334f3931a11d)
LoadBalancerClient 默认的实现类为基于 SCL 的 BlockingLoadBalancerClient,其定义如下:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_88_3.jpg?sign=1739031645-GSnafxCzbOBcZhCEQPHgSNJaB8xY0hPY-0-a5f17888e6a7c0d6b2f8b9055598a298)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_89_1.jpg?sign=1739031645-TDqtC5suCg5yKaFqYX7kzDHPUfddB0FE-0-c1b8e2a09f0f622f0447415760485a24)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_90_1.jpg?sign=1739031645-bfDKDsdeFJN2peM7J07TSTXzVqgKEb2D-0-93cdc3335dad20ab6d5ad22e062772b2)
上述代码中:
① BlockingLoadBalancerClient 构造函数依赖 LoadBalancerClientFactory。LoadBalancer-ClientFactory是一个用于创建 ReactiveLoadBalancer的工厂类,LoadBalancerClientFactory内部维护着一个 Map,该 Map用于保存各个服务的 ApplicationContext(Map的 key是服务名)。每个ApplicationContext内部维护对应服务的一些配置和Bean。
② 没有 ServiceInstance参数的 execute方法内部会调用 choose方法获取 ServiceInstance,然后调用另外一个重载的execute方法。
③ 有 ServiceInstance参数的 execute方法把负载均衡的操作直接委托给 LoadBalancerRequest负载均衡请求处理。
④ 根据 URI 和找到的服务实例 ServiceInstance 重新构造一个 URI,这个过程被封装在LoadBalancerUriTools工具类里。
⑤代码②处提到的choose方法会返回服务实例ServiceInstance。choose方法首先会根据服务名和 LoadBalancerClientFactory 得到该服务名所对应的 ReactiveLoadBalancer Bean,然后调用ReactiveLoadBalancer的choose方法得到服务实例ServiceInstance。
4.Spring Cloud LoadBalancer总结
下面对Spring Cloud LoadBalancer内容做个总结。
①@LoadBalanced 注解修饰 RestTemplate 后,会根据 RestTemplateCustomized 注解给RestTemplate 做定制化操作。这个定制化操作一定含有一个添加 LoadBalancerInterceptor 负载均衡拦截器的操作。此外,我们还可以扩展添加符合业务需求的自定义定制化操作。整个过程如图3-2所示。
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_91_1.jpg?sign=1739031645-0JWXd5XuIycSgK8SekosikR7ii9i122t-0-4c2ae3015d4c83f1e6066387c4ee51ec)
图3-2
② LoadBalancerInterceptor负载均衡拦截器拦截的背后会通过 LoadBalancerClient的execute方法完成最终的调用。execute 需要两个参数,一个是服务名,请求信息中的 host 即表示服务名;另一个是负载均衡请求(LoadBalancerRequest),通过 LoadBalancer-RequestFactory构造完成。整个过程如图3-3所示。
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_91_2.jpg?sign=1739031645-YhQDnGLpazFgtEhBUDO059e8Y3n87CXk-0-80973aefccad5ca9e1a6379e63e0c1e7)
图3-3
Spring Cloud LoadBalancer还提供了@LoadBalancerClient注解用于进行自定义的配置操作,如自定义负载均衡算法(默认是轮询算法)、自定义 ServiceInstanceListSupplier(默认会缓存服务实例列表)。
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_92_1.jpg?sign=1739031645-Oq4OLj08ZmnoVm0iQzZrkG0mtChRK1xX-0-7204e2d10106e9b887c4c28f40d7389b)
@LoadBalancerClient注解有3个属性,分别是value:String、name:String和configuration:Class[]。name 和 value 属性表示同一个含义,即服务名,且只能设置其中一个属性。上述代码中,nacos-provider-lb对应的是服务名,每个服务名拥有单独的自定义配置。
提示:@LoadBalancerClients 注解的 defaultConfiguration 属性表示默认的配置类,所有的BlockingLoadBalancerClient都会使用这些配置类里的配置。
configuration属性表示配置类,配置类中返回的 Bean会替换 LoadBalancerClientConfiguration配置类中已经存在的Bean(前文提到LoadBalancerClientFactory 内部维护着一个Map,用于保存各个服务的 ApplicationContext。每个 ApplicationContext构造的时候都会加上 LoadBalancer-ClientConfiguration 配置类)。比如,LoadBalancerClientConfiguration 配置类中负载均衡策略为默认的轮询策略:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_92_2.jpg?sign=1739031645-Yq3fjlcGJndApmx6TpmW49NpKpMTZB1g-0-1c80cf66a9efdc1c4ee7856a3318ceef)
上述代码中:
① 默认的负载均衡策略 Bean 被 ConditionalOnMissingBean 注解修饰,表示开发者配置的配置类优先级更高。
②默认使用轮询负载均衡策略。
我们可以在 MyLoadBalancerConfiguration 配置类中定义一个新的 ReactorLoadBalancer 来覆盖默认的负载均衡策略:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_93_1.jpg?sign=1739031645-alzU2TbE4TtnVjVhOQZ5Z7TKFGO1ucoX-0-462a5fe02652db6ce7b4f8f529e1a166)
RandomLoadBalancer是一个自定义的实现随机算法的负载均衡策略:
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_93_2.jpg?sign=1739031645-LukDzt9EIJcOXCH3gTWP21qhTUsWKq9R-0-faae28c227f72374762b79978722ef4c)
![](https://epubservercos.yuewen.com/8E18D1/21440186401518706/epubprivate/OEBPS/Images/39973_94_1.jpg?sign=1739031645-YGCHxoIFrQyYWFsR25dn9oVKAdzsjDTp-0-c9b22f8ad05542eb12f94e60ad22982c)