Spring Cloud Ribbon源码分析

客户端负载均衡

学习spring cloud ribbon的时候不得不提到客户端负载均衡。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心。客户端发送一个请求经过客户端负载均衡器后发向不同的服务端。

RestTemplate

Spring Cloud Ribbon实现客户端负载均衡的方法是带有@LoadBalanced注解的RestTemplate对象来实现的,只要带上这个注解,发出的rest请求就会经过客户端负载均衡。

Ribbon

Spring Cloud Ribbon 基于Netflix 的 Ribbon实现,Spring Cloud对其封装了,提供了统一的接口,而最终实现是netfilx的ribbon实现。

源码分析

首先我们看下这个@LoadBalanced注解的源码:

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

从注释可以看出使用这个注解是为了能使用LoadBalancerClient,即负载均衡客户端,这个接口的源码如下:

public interface LoadBalancerClient extends ServiceInstanceChooser {

    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    URI reconstructURI(ServiceInstance instance, URI original);
}

execute方法:使用从负载均衡器中挑选出的服务实例来执行请求内容。
reconstructURI方法:为系统构建一个合适的host:port形式的uri。

接下来来看下为实现客户端负载均衡器的自动化配置类LoadBalancerAutoConfiguration

/**
 * Auto configuration for Ribbon (client side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 * @author Gang Li
 */
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
            final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            @Override
            public void afterSingletonsInstantiated() {
                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                    for (RestTemplateCustomizer customizer : customizers) {
                        customizer.customize(restTemplate);
                    }
                }
            }
        };
    }

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
    }

    @Configuration
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }

    @Configuration
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public RetryTemplate retryTemplate() {
            RetryTemplate template =  new RetryTemplate();
            template.setThrowLastExceptionOnExhausted(true);
            return template;
        }

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
            return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
        }

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory() {
            return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
        }

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory() {
            return new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
        }
    }

    @Configuration
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryInterceptorAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
                LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
                LoadBalancerRequestFactory requestFactory,
                LoadBalancedBackOffPolicyFactory backOffPolicyFactory,
                LoadBalancedRetryListenerFactory retryListenerFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
                    lbRetryPolicyFactory, requestFactory, backOffPolicyFactory, retryListenerFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }
}

这个类的注释@ConditionalOnClass(RestTemplate.class)表示RestTemplate类必须存在于当前工程的环境中;@ConditionalOnBean(LoadBalancerClient.class)表示当前工程中必须要有LoadBalancerClient的实现bean。
我们先从这个类内部的一个静态类开始分析:

@Configuration
   @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
   static class LoadBalancerInterceptorConfig {
       @Bean
       public LoadBalancerInterceptor ribbonInterceptor(
               LoadBalancerClient loadBalancerClient,
               LoadBalancerRequestFactory requestFactory) {
           return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
       }

       @Bean
       @ConditionalOnMissingBean
       public RestTemplateCustomizer restTemplateCustomizer(
               final LoadBalancerInterceptor loadBalancerInterceptor) {
           return new RestTemplateCustomizer() {
               @Override
               public void customize(RestTemplate restTemplate) {
                   List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                           restTemplate.getInterceptors());
                   list.add(loadBalancerInterceptor);
                   restTemplate.setInterceptors(list);
               }
           };
       }
   }

这个类主要做了两件事:

  1. 创建一个LoadBalancerInterceptor的bean。
  2. 创建一个RestTemplateCustomizer的bean,并将1中创建的拦截器设置到传入的restTemplate对象中。
    回到主类LoadBalancerAutoConfiguration的开头,可以看到LoadBalancerAutoConfiguration这个类维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,在loadBalancedRestTemplateInitializer方法中,使用我们上面提到的RestTemplateCustomizer实例来给RestTemplate对象添加LoadBalancerInterceptor拦截器。

那这个拦截器的具体是怎么实现负载均衡的呢,我们来看这个拦截器的源码:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

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

当RestTemplate对象发送一个请求时,首先会执行intercept方法。从上面的负载均衡自动化配置类中可以知道,拦截器类LoadBalancerInterceptor中的LoadBalancerClient对象是从外界传入的,而LoadBalancerInterceptor是一个接口,所以在拦截器的intercept方法中执行的LoadBalancerClient对象的execute方法是某个具体的LoadBalancerClient的实现类完成的,针对ribbon的负载均衡器这个接口对应的实现类为RibbonLoadBalancerClient,这个类在netfilx.ribbon包下。
接下来我们就来看下RibbonLoadBalancerClient客户端的execute方法实现:

@Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
                serviceId), serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

@Override
    public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
        Server server = null;
        if(serviceInstance instanceof RibbonServer) {
            server = ((RibbonServer)serviceInstance).getServer();
        }
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }

        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        try {
            T returnVal = request.apply(serviceInstance);
            statsRecorder.recordStats(returnVal);
            return returnVal;
        }
        // catch IOException and rethrow so RestTemplate behaves correctly
        catch (IOException ex) {
            statsRecorder.recordStats(ex);
            throw ex;
        }
        catch (Exception ex) {
            statsRecorder.recordStats(ex);
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }
  • 从源码可知getLoadBalancer完成了通过传入的服务名来选择具体的负载均衡器,代码中的ILoadBalancer负载均衡器是一个接口,ribbon的所有负载均衡器实现类都是这个接口的实现类,不同的负载均衡器对应着不同的负载均衡策略。ribbon在整合spring cloud的时候ILoadBalancer接口的默认实现类为ZoneAwareLoadBalancer类。
  • 另外使用getServer方法从负载均衡器中获取的服务对象类Server类定义了一个传统的服务端节点,在该类中存储了服务端节点的一些元数据信息,包括host,port以及一些部署信息。
  • 此外代码中提及的另一个类RibbonServer是对Server类进行的包装,它还保存了服务名serviceid,是否需要使用https等其他信息。
  • execute方法最终执行的是request.apply(serviceInstance);这句代码通过调用LoadBalancerRequest请求的apply方法来实现请求的访问。apply方法传入的ServiceInstance接口对象是对服务实例的抽象定义。从代码上可知RibbonServer类就是ServiceInstance接口的实现。另外LoadBalancerRequest是一个接口,它的具体实现要回溯到LoadBalancerInterceptor拦截器中的传参:requestFactory.createRequest(request, body, execution)。而requestFactory是从LoadBalancerAutoConfiguration配置类传过来的,回溯到这个自动化配置类中发现实际传入的对象是LoadBalancerRequestFactory对象。下面我们看下LoadBalancerRequestFactory类的实现:
public class LoadBalancerRequestFactory {

    private LoadBalancerClient loadBalancer;
    private List<LoadBalancerRequestTransformer> transformers;

    public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer,
            List<LoadBalancerRequestTransformer> transformers) {
        this.loadBalancer = loadBalancer;
        this.transformers = transformers;
    }

    public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
            final byte[] body, final ClientHttpRequestExecution execution) {
        return new LoadBalancerRequest<ClientHttpResponse>() {

            @Override
            public ClientHttpResponse apply(final ServiceInstance instance)
                    throws Exception {
                HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
                if (transformers != null) {
                    for (LoadBalancerRequestTransformer transformer : transformers) {
                        serviceRequest = transformer.transformRequest(serviceRequest, instance);
                    }
                }
                return execution.execute(serviceRequest, body);
            }

        };
    }
}

参考网站

https://blog.csdn.net/u012702547/article/details/77940838

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,999评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,102评论 1 302
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,709评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,439评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,846评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,881评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,062评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,783评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,517评论 1 248
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,762评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,241评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,236评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,145评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,941评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,965评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,802评论 2 275

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,106评论 18 139
  • 断断续续看Ribbon的源码差不多也有7-8天了,总算告一段落。本文记录了这些天对源码的阅读过程与一些分析理解,如...
    程序猿DD阅读 6,508评论 6 11
  • 简介 Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix ...
    Chandler_珏瑜阅读 248,610评论 22 183
  • 软件是有生命的,你做出来的架构决定了这个软件它这一生是坎坷还是幸福。 本文不是讲解如何使用Spring Cloud...
    Bobby0322阅读 22,459评论 3 165
  • 微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务。但如果要将微服务架构运用到生产项目上,并且能够发挥...
    程序员技术圈阅读 2,751评论 10 27