SpringCloud超详细讲解负载均衡组件Ribbon源码

作者:_时光煮雨 时间:2021-06-17 18:39:47 

前言

上一篇文章中我们通过自己开发了一个负载均衡组件,实现了随机算法的负载均衡功能,如果要实现其他算法,还需要修改代码增加相应的功能。这一篇文章,我们将介绍一个更简单的负载均衡实现,使用**@LoadBalanced**注解实现负载均衡的功能。

项目实战

创建项目

同样的,我们的项目现在依然有一个registry注册中心,一个provider服务提供者,接下来,我们再次修改一下consumer服务消费者的代码:

@EnableEurekaClient
@SpringBootApplication
@RestController
public class ConsumerApplication {
   public static void main(String[] args) {
       SpringApplication.run(ConsumerApplication.class, args);
   }
   @Bean
   @LoadBalanced
   RestTemplate restTemplate() {
       return new RestTemplate();
   }
   @Autowired
   DiscoveryClient discoveryClient;
   @Autowired
   RestTemplate restTemplate;
   @GetMapping("/hello2")
   public String hello2(String name) {
       String returnInfo = restTemplate.getForObject(  "http://provider/hello?name={1}", String.class, name);
       return returnInfo;
   }
}

在这个版本,同样的,还是创建RestTemplate Bean对象,不同的是上面仅仅增加了注解。

启动项目验证

SpringCloud超详细讲解负载均衡组件Ribbon源码

依然正确返回了结果!

太神奇了吧,比我们自己开发的负载均衡组件简单太多了吧,仅仅在restTemplate() 方法上面增加了一个@LoadBalanced注解,怎么就实现的呢?废话不说,为了一探究竟,扒一扒源码吧!

源码分析

首先,点击@LoadBalanced注解进去,没有什么特别之处,那么我们在想想,Spring在创建Bean实例的时候,注解在什么地方起了作用?什么?不知道?翻一下这篇文章吧:

肝了两周,一张图解锁Spring核心源码

通过回顾Spring启动以及Bean的生命周期创建过程,我们就会发现加上@LoadBalancer注解后,项目启动时就会加载LoadBalancerAutoConfiguration这个配置类(通过spring-cloud-commons包下面的的spring.factories)。通过查看该配置类源码,发现其有个静态内部类LoadBalancerInterceptorConfig,其内部又创建了一个负载均衡 * :LoadBalancerInterceptor,该 * 包含有一个loadBalancerClient参数:

@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
   static class LoadBalancerInterceptorConfig {
       LoadBalancerInterceptorConfig() {
       }
       @Bean
       public LoadBalancerInterceptor ribbonInterceptor(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);
           };
       }
   }

我们继续点击LoadBalancerInterceptor类进入,发现intercept方法,该方法中调用了LoadBalancerClient的execute方法,

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
       URI originalUri = request.getURI();
       String serviceName = originalUri.getHost();
       Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
       return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
   }

LoadBalancerClient是一个接口,点击进去我们发现其实现类是RibbonLoadBalancerClient,查看其继承关系:

SpringCloud超详细讲解负载均衡组件Ribbon源码

通过接口中的方法名称,我们可以猜想,choose方法就是选择其中服务列表中其中一个服务,reconstructURI方法就是重新构造请求的URI。

选择服务

choose方法是在RibbonLoadBalancerClient实现类中实现的

public ServiceInstance choose(String serviceId, Object hint) {
       Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
       return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
   }

在这个方法中,先调用 getServer 方法获取服务,这个方法最终会调用 ILoadBalancer 接口的 chooseServer 方法,而 ILoadBalancer 接口的实现类默认是ZoneAwareLoadBalancer。

ZoneAwareLoadBalancer 继承自 DynamicServerListLoadBalancer ,而在 DynamicServerListLoadBalancer 的构造方法中,调用了 this.restOfInit(clientConfig);在restOfInit这个方法中,通过 this.updateListOfServers()来获取服务列表;

而在chooseServer ()方法中,就会根据负载均衡算法,选择其中一个服务并返回:

BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);

地址替换

选择其中一个服务信息后,怎么将接口从 http://provider/hello 变为 http://localhost:8003/hello 呢?还记得上面我们说的reconstructURI方法吗?通过配置类LoadBalancerAutoConfiguration加载后,会注入LoadBalancerInterceptor * ,该 * 会拦截我们的请求,并对请求地址进行处理,重构方法的具体实现在 LoadBalancerContext 类的 reconstructURIWithServer 方法中

public URI reconstructURIWithServer(Server server, URI original) {
       String host = server.getHost();
       int port = server.getPort();
       String scheme = server.getScheme();
       if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
           return original;
       } else {
           if (scheme == null) {
               scheme = original.getScheme();
           }
           if (scheme == null) {
               scheme = (String)this.deriveSchemeAndPortFromPartialUri(original).first();
           }
           try {
               StringBuilder sb = new StringBuilder();
               sb.append(scheme).append("://");
               if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
                   sb.append(original.getRawUserInfo()).append("@");
               }
               sb.append(host);
               if (port >= 0) {
                   sb.append(":").append(port);
               }
               sb.append(original.getRawPath());
               if (!Strings.isNullOrEmpty(original.getRawQuery())) {
                   sb.append("?").append(original.getRawQuery());
               }
               if (!Strings.isNullOrEmpty(original.getRawFragment())) {
                   sb.append("#").append(original.getRawFragment());
               }
               URI newURI = new URI(sb.toString());
               return newURI;
           } catch (URISyntaxException var8) {
               throw new RuntimeException(var8);
           }
       }
   }

可以看到该方法中,将原始的请求地址original,替换成了选取的服务的IP和端口。并最终调用该服务的接口方法。

看到这里,再想想我们上一章的内容,是不是有异曲同工之妙?

来源:https://coder965.blog.csdn.net/article/details/125305901

标签:SpringCloud,负载均衡,组件,Ribbon
0
投稿

猜你喜欢

  • 基于Spring Boot保护Web应用程序

    2022-11-15 19:14:48
  • 详解springboot项目带Tomcat和不带Tomcat的两种打包方式

    2023-11-28 08:23:41
  • java中replaceAll替换圆括号实例代码

    2023-09-30 16:29:54
  • 简单谈谈Java中的栈和堆

    2022-07-30 05:33:01
  • Java构建JDBC应用程序的实例操作

    2023-08-07 12:09:13
  • Spring Boot教程之必须了解的核心概念

    2022-07-15 14:17:24
  • Java实现线程同步方法及原理详解

    2021-07-29 21:28:13
  • Java并发编程示例(一):线程的创建和执行

    2022-01-24 16:43:49
  • SpringBoot整合Elasticsearch游标查询的示例代码(scroll)

    2022-02-11 02:02:13
  • Spring Boot教程之提高开发效率必备工具lombok

    2021-08-23 11:12:43
  • c# 使用Task实现非阻塞式的I/O操作

    2023-07-21 23:27:39
  • 详解C#多线程编程之进程与线程

    2021-07-18 14:15:36
  • 使用java实现http多线程断点下载文件(一)

    2023-11-23 15:45:51
  • SpringBoot超详细讲解@Value注解

    2022-03-06 12:05:36
  • Java线程池的优点及池化技术的应用

    2022-07-01 08:12:23
  • 使用Spring自定义实现IOC和依赖注入(注解方式)

    2023-09-16 04:42:35
  • Java如何基于IO流实现同一文件读写操作

    2023-07-30 15:06:38
  • Volatile关键字的使用案例

    2022-01-27 16:37:05
  • 一起来学习C#的观察者模式

    2022-04-02 13:43:15
  • java swing实现简单计算器界面

    2021-11-09 12:47:05
  • asp之家 软件编程 m.aspxhome.com