Ribbon LoadBalanced底层机制源码探秘
作者:mmseoamin日期:2023-12-25

🍊 Java学习:社区快速通道


🍊 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想


🍊 绝对不一样的职场干货:大厂最佳实践经验指南


📆 最近更新:2023年6月18日


🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!


文章目录

  • 负载均衡器LoadBalancer原理
  • Ribbon LoadBalanced底层机制源码探秘

    通过本文你可以学习到:

    1. LoadBalanced作用原理
    2. 拦截器到Rule的调角链路
    3. IPing机制

    负载均衡器LoadBalancer原理

    一句话概括: 由LoadBalanced在RestTemplate上打标,Ribbon将带有负载均衡能力的拦截器注入标记好的RestTemplate中,以此实现负载均衡。

    从@LoadBalanced开始看起:

    它会将RestTemplate传送到Ribbon的自动装配类里进行改造。

    Ribbon LoadBalanced底层机制源码探秘,在这里插入图片描述,第1张

    • @LoadBalanced:这个注解即修饰RestTemplate,还修饰LoadBalancerAutoConfiguration。它会将所有带有LoadBalanced注解的RestTemplate类,都传入到LoadBalancerAutoConfiguration中。这个注解的定义上还有一个@Qualifier注解,@Qualifier注解搭配@Autowired注解做自动装配,可以通过name属性,将指定的Bean装载到指定位置。

      这里LoadBalanced也是借助Qualifier实现了一个给RestTemplate打标的功能,凡是被打标的RestTemplate都会被传送到AutoConfig中做进一步改造。

      • LBAutoConfig:从上一步中传送过来的RestTemplate,会经过LBAutoConfig的装配,将一系列的拦截器添加到RestTemplate中。Ribbon拦截器会拦截每个网络请求做一番处理,在这个过程中拦截器会找到对应的LoadBalancer对HTTP请求进行接管,接着LoadBalancer就会找到默认或指定的负载均衡策略来对HTTP请求进行转发。

        拦截器是类似职责链设计模型的结构,常见的ServletFilter,权限控制器等都是类似的模式。

        Ribbon LoadBalanced底层机制源码探秘

        点进LoadBalanced注解,查到LoadBalancerAutoConfiguration使用了该注解

        Ribbon LoadBalanced底层机制源码探秘,请添加图片描述,第2张

        该注解可以把修饰的restTemplate传送到LoadBalancerAutoConfiguration里

        这个restTemplate只在loadBalancedRestTemplateInitializerDeprecated方法里被用到

        @Bean
        public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider> restTemplateCustomizers) {
            return () -> {
                restTemplateCustomizers.ifAvailable((customizers) -> {
                    Iterator var2 = this.restTemplates.iterator();
                    while(var2.hasNext()) {
                        RestTemplate restTemplate = (RestTemplate)var2.next();
                        Iterator var4 = customizers.iterator();
                        while(var4.hasNext()) {
                            RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
                            customizer.customize(restTemplate);
                        }
                    }
                });
            };
        }
        

        循环访问所有的restTemplate,restTemplateCustomizers是由外面初始化的bean注入进来的,使用customizer对restTemplate做手脚

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List list = new ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
        

        先从restTemplate获取getInterceptors(),接下来list里添加一个loadBalancerInterceptor,它的注入:

        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
        
        public void setInterceptors(List interceptors) {
            if (this.interceptors != interceptors) {
                this.interceptors.clear();
                this.interceptors.addAll(interceptors);
                AnnotationAwareOrderComparator.sort(this.interceptors);
            }
        }
        

        这里把 interceptors 和本地保存的做一下比较,如果不一样则本地的interceptors全部清空,然后添加上新的,再sort一下


        真正起作用的位置是在LoadBalancerInterceptor的intercept方法上

        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));
        }
        

        先从url中得到uri,再从uri里得到serviceName(要去访问的serviceName),然后执行execute方法

        public  T execute(String serviceId, LoadBalancerRequest request, Object hint) throws IOException {
            ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
            Server server = this.getServer(loadBalancer, hint);
            if (server == null) {
                throw new IllegalStateException("No instances available for " + serviceId);
            } else {
                RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
                return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
            }
        }
        

        这里到了真正执行任务的时候了,先根据serviceId获取一个LoadBalancer,拿到负载均衡策略之后用getServer获取到真正的server

        protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
            return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
        }
        
        @Override
        public Server chooseServer(Object key) {
            if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
                logger.debug("Zone aware logic disabled or there is only one zone");
                return super.chooseServer(key);
            }
            Server server = null;
            try {
                LoadBalancerStats lbStats = getLoadBalancerStats();
                Map zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
                logger.debug("Zone snapshots: {}", zoneSnapshot);
                if (triggeringLoad == null) {
                    triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                            "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
                }
                if (triggeringBlackoutPercentage == null) {
                    triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                            "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
                }
                Set availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
                logger.debug("Available zones: {}", availableZones);
                if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                    String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                    logger.debug("Zone chosen: {}", zone);
                    if (zone != null) {
                        BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                        server = zoneLoadBalancer.chooseServer(key);
                    }
                }
            } catch (Exception e) {
                logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
            }
            if (server != null) {
                return server;
            } else {
                logger.debug("Zone avoidance logic is not invoked.");
                return super.chooseServer(key);
            }
        }
        

        如果只定义了一个defaultZone,则会调用父类的chooseServer方法

        public Server chooseServer(Object key) {
            if (counter == null) {
                counter = createCounter();
            }
            counter.increment();
            if (rule == null) {
                return null;
            } else {
                try {
                    return rule.choose(key);
                } catch (Exception e) {
                    logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                    return null;
                }
            }
        }
        

        这里使用默认的负载均衡策略RandomRule


        回到RibbonLoadBalancerClient的execute方法,获取到的服务器不为空则:

        RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
        return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
        

        构建一个RibbonServer,最后execute就是真正发起请求了