SpringCloud Ribbon 客户端负载均衡原理
相比于nginx, nginx是在服务端进行赋值均衡,由nginx服务器通过负载均衡算法获取到某个服务提供者的节点后,再将请求转发到该节点,而ribbon是在客户端进行负载均衡,也就是服务的调用方,获取到服务的提供者的地址列表,通过某种负载均衡算法,选择其中一个服务提供者节点进行服务调用, 下面通过两个项目来学习Ribbon 负载均衡的实现以及原理;
Ribbon实现负载均衡的两种方式通过LoadBalancerClient 实现负载均衡 通过@LoadBalanced注解实现负载均衡
创建两个项目order-service user-service,一个作为服务提供者,一个作为服务消费者 服务提供者order-service: 配置文件application.properties文件中配置端口号: server.port=8080 ,启动服务,然后修改端口号为8082,再启动一个服务,这样就得到了一个服务提供者的集群 服务消费者user-service: package com.gupaoedu.springcloud.example.springclouduserservice; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserController { @Autowired RestTemplate restTemplate; @Bean public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){ return restTemplateBuilder.build(); } @Autowired LoadBalancerClient loadBalancerClient; @GetMapping("/user/{id}") public String findById(@PathVariable("id")int id){ //TODO // 调用订单的服务获得订单信息 // HttpClient RestTemplate OkHttp JDK HttpUrlConnection ServiceInstance serviceInstance=loadBalancerClient.choose("spring-cloud-order-service"); String url=String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/orders"); return restTemplate.getForObject(url,String.class); } }配置文件: 指定服务消费者端口,以及服务提供者的地址列表,ribbon会读取spring-cloud-order-service.ribbon.listOfServers 这个配置项获取服务提供者的集群地址列表
server.port=8088 # 配置指定服务的提供者的地址列表(服务集群列表) spring-cloud-order-service.ribbon.listOfServers=\ localhost:8080,localhost:8082然后启动项目,不断访问http://localhost:8088/user/1 , 会看到order-service项目的两个控制台轮流打印端口号(ribbon默认的负载均衡算法是轮询) 上面是通过LoadBalancerClient 实现负载均衡,LoadBalancerClient 可以使用@LoadBalanced注解来代替,只需要在RestTemplate Bean的定义上面加上 @LoadBalanced注解即可:
package com.gupaoedu.springcloud.example.springclouduserservice; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserController { @Autowired RestTemplate restTemplate; @Bean @LoadBalanced public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){ return restTemplateBuilder.build(); } // @Autowired // LoadBalancerClient loadBalancerClient; @GetMapping("/user/{id}") public String findById(@PathVariable("id")int id){ return restTemplate.getForObject("http://spring-cloud-order-service/orders",String.class); } }有个细节需要注意到,就是上面的请求地址是改成了http://spring-cloud-order-service/orders ,即 http://服务名/接口名的形式。 那么这个注解是如何实现负载均衡的,我们暂时不得而知,但是可以肯定的是,它肯定实现了以下功能:
通过服务名来解析服务地址列表在通过restTemplate 进行http请求调用前,修改请求url通过restTemplate 进行http请求调用,返回结果查看LoadBalanced 注解,可以看到它本质上就是一个@Qualifier注解,这个注解的作用就是给Bean打上一个标记(详见https://blog.csdn.net/weixin_41300437/article/details/109192723)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }并不能看出什么端倪,从springboot自动装配原理,我们猜想应该有一个负载均衡的配置类,在IDEA中全局搜索LoadBalance, 找到LoadBalancerAutoConfiguration 这个自动配置类
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired(equired = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); public LoadBalancerAutoConfiguration() { }可以看到restTemplates 上面加了 @Autowired注解和一个@LoadBalanced注解,也就等同于加了一个@Qualifier注解,那么我们就能得出一个结论,只要是加了@LoadBalanced 的restTemplate bean, 那么都会被添加到restTemplates 这个list中。不加@LoadBalanced注解的,就不能被添加进去。LoadBalancerAutoConfiguration 这个自动装配类,会在spring容器启动时,完成ribbon所需要的一些bean的自动装配,完整代码如下:
package org.springframework.cloud.client.loadbalancer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestTemplate; /** * Auto-configuration for Ribbon (client-side load balancing). * * @author Spencer Gibb * @author Dave Syer * @author Will Tran * @author Gang Li */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { // 将所有加了@LoadBalanced注解的restTemplate bean放到restTemplates这个list里面 @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); // 3. 遍历restTemplates,用RestTemplateCustomizer对每一个加了@LoadBalanced注解的restTemplate进行包装 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { // 1.定义LoadBalancerInterceptor 拦截器Bean @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } // 2.对restTemplate进行包装,依赖注入loadBalancerInterceptor 这个bean,对加了@LoadBalanced注解的Restemplate实例添加LoadBalancerInterceptor拦截器 @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); // 获取restTemplate默认的拦截器 list.add(loadBalancerInterceptor); // 对restTemplate添加新的拦截器 restTemplate.setInterceptors(list); // 将新的拦截器链设置到restTemplate里 }; } } /** * Auto configuration for retry mechanism. */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RetryTemplate.class) public static class RetryAutoConfiguration { @Bean @ConditionalOnMissingBean public LoadBalancedRetryFactory loadBalancedRetryFactory() { return new LoadBalancedRetryFactory() { }; } } /** * Auto configuration for retry intercepting mechanism. */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RetryTemplate.class) public static class RetryInterceptorAutoConfiguration { @Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory) { return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory, loadBalancedRetryFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final RetryLoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } }