spring cloud ribbon学习二:Ribbon轮询策略源码分析

看spring cloud源码分析好绕,还是坚持看完了。

先总结一下Ribbon的运行流程,可以跳过总结看下面,然后重新看总结。

  • 项目启动的时候会自动的为我们加载LoadBalancerAutoConfiguration自动配置类,该自动配置类初始化条件是要求classpath必须要有RestTemplate这个类,必须要有LoadBalancerClient实现类。
  • LoadBalancerAutoConfiguration为我们干了二件事,第一件是创建了LoadBalancerInterceptor拦截器bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。创建了一个
    RestTemplateCustomizer的bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。
  • 每次请求的时候都会执行org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptorintercept方法,而LoadBalancerInterceptor具有LoadBalancerClient(客户端负载客户端)实例的一个引用,
    在拦截器中通过方法获取服务名的请求url(比如http://user-service/user),及服务名(比如user-service),然后调用负载均衡客户端的execute方法。
  • 执行负载客户端RibbonLoadBalancerClient(LoadBalancerClient的实现)的execute方法,得到ILoadBalancer(负载均衡器)的实现ZoneAwareLoadBalancer,并且通过调用其chooseServer方法获得服务列表中的一个实例,比如说user-service列表注册到eureka中一个实例。然后向其中的一个具体实例发起请求,得到结果。

源码分析

之前我们实现负载均衡是在消费端的RestTemplate加上注解@LoadBalanced,便可以实现负载均衡了

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}

查看注解内容:

/**
 * 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 {
}

这个注解给RestTemplate做标记,标记为LoadBalancerClient

查看LoadBalancerClient源码:

/**
 * Represents a client side load balancer
 * @author Spencer Gibb
 */
public interface LoadBalancerClient extends ServiceInstanceChooser {

    /**
     * 通过LoadBalancer的ServiceInstance对指定的服务执行请求操作
     */
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

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

    /**
     * 为系统构建一个合适的host:port形式的url。在分布式系统中,我们使用逻辑上的服务名称作为host来构建URI
     * (替代服务实例的host:port形式)进行请求,比如说myservice/path/to/service。
     */
    URI reconstructURI(ServiceInstance instance, URI original);
}

继承自接口ServiceInstanceChooser

/**
 * Implemented by classes which use a load balancer to choose a server to
 * send a request to.
 *
 * @author Ryan Baxter
 */
public interface ServiceInstanceChooser {

    /**
     * Choose a ServiceInstance from the LoadBalancer for the specified service
     * @param serviceId the service id to look up the LoadBalancer
     * @return a ServiceInstance that matches the serviceId
     */
    ServiceInstance choose(String serviceId);
}
  • ServiceInstance choose(String serviceId):根据传入的服务名serviceId,从负载均衡器中挑选一个对应服务的实例。

  • T execute(String serviceId, LoadBalancerRequest<T> request):使用从负载均衡器中挑选出来的服务实例来执行请求内容。

  • T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request):使用从负载均衡器中挑选出来的服务实例来执行请求内容。

  • URI reconstructURI(ServiceInstance instance, URI original):为系统构建一个合适的host:port形式的url。在分布式系统中,我们使用逻辑上的服务名称作为host来构建URI(替代服务实例的host:port形式)进行请求,比如说myservice/path/to/service。

顺着LoadBalancerClient接口的所属包org.springframework.cloud.client.loadbalancer,我们对内容进行整理,可以得到下面的关系:

LoadBalancerAutoConfiguration为客户端Ribbon负载均衡的自动化配置类,

@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) {
                    //通过调用RestTemplateCustomizer的实例来给需要的客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。
                        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 {
       //创建了一个LoadBalancerInterceptor的bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            //创建了一个RestTemplateCustomizer的bean,用于给RestTemplate增加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):当前项目的classpath路径下有RestTemplate这个类。

@ConditionalOnBean(LoadBalancerClient.class):spring容器中必须有LoadBalancerClient的实现bean

该自动化配置主要完成了三件事

  • 创建了一个LoadBalancerInterceptor的bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
  • 创建了一个RestTemplateCustomizer的bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。
  • 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行维护,通过调用RestTemplateCustomizer的实例来给需要的客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

看看LoadBalancerInterceptor拦截器是如何让一个普通的RestTemplate变成负载均衡的:

LoadBalancerInterceptor拦截器

LoadBalancerClient是一个抽象的接口,originalUri.getHost()获取到的是服务名,execute函数去根据服务名来选择实例并发起实际的请求。

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient是实现LoadBalancerClient接口,看其实现:
execute方法,使用从负载均衡器中挑选出来的服务实例来执行请求内容。

execute方法

getServer方法:

getServer方法

去调用ILoadBalancer实例的chooseServer方法

认识一下com.netflix.loadbalancer.ILoadBalancer接口:
ILoadBalancer负载均衡器

Interface that defines the operations for a software loadbalancer. A typical
loadbalancer minimally need a set of servers to loadbalance for, a method to
mark a particular server to be out of rotation and a call that will choose a
server from the existing list of server.
定义软件负载平衡器操作的接口。 一个典型的负载均衡器最低限度地需要一组服务器来负载平衡,一种方法标记一个特定的服务器,以避免旋转和葱已有的服务列表中选择一个实例进行调用。

public interface ILoadBalancer {

    //向负载均衡器中维护的实例列表增加服务实例
    public void addServers(List<Server> newServers);
    
    //从负载均衡器中挑选出一个具体的服务实例
    public Server chooseServer(Object key);
    
    //用来通知和标记负载均衡器中的某个具体实例已经停止服务,不然负载均衡器在下一次获取服务实例清单前都会认为服务实例均是正常服务的。
    public void markServerDown(Server server);
    
    /**
     * @deprecated 2016-01-20 This method is deprecated in favor of the
     * cleaner {@link #getReachableServers} (equivalent to availableOnly=true)
     * and {@link #getAllServers} API (equivalent to availableOnly=false).
     *
     * Get the current list of servers.
     *
     * @param availableOnly if true, only live and available servers should be returned
     */
    @Deprecated
    public List<Server> getServerList(boolean availableOnly);

    //获取当前正常服务的实例列表
    public List<Server> getReachableServers();

   //获取所有已知的服务实例列表,包括正常服务和停止服务实例。
    public List<Server> getAllServers();
}

com.netflix.loadbalancer.Server对象定义是一个传统的服务端节点,在该类中存储了服务节点的一些元数据信息,包括host,post以及一些部署信息等。

com.netflix.loadbalancer.ILoadBalancer接口的一些实现,

ILoadBalancer的一些实现类

springcloud整合Ribbon的时候选择采用的是com.netflix.loadbalancer.ZoneAwareLoadBalancer负载均衡器。调用它的chooseServer方法。

ZoneAwareLoadBalancer的chooseServer方法

回到org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClientexecute方法,

public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        //从上面跟过来我们知道这边的serviceId其实就是服务名
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        //通过ZoneAwareLoadBalancer的chooseServer函数获取了负载均衡策略分配的服务实例对象Server之后,将其包装成RibbonServer(增加了服务名serverid,是否需要使用Https等其他信息)
        Server server = this.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);
}

ILoadBalancer的实现com.netflix.loadbalancer.ZoneAwareLoadBalancer,将其包装成RibbonServer,调用ZoneAwareLoadBalancerchooseServer函数。

回到org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClientexecute方法,调用LoadBalancerRequestapply方法,向一个实际的具体服务实例发起请求,从而实现一开始以服务名为host的URI请求到host:port形式的实际访问地址的转换

图片.png

apply方法参数ServiceInstance实例,ServiceInstance

ServiceInstance接口

上面说到的RibbonServer对象就是ServiceInstance接口的实现

RibbonServer实现

我们已经可以大概理清了Spring Cloud Ribbon中实现客户端负载均衡的基本脉络,了解它是如何通过LoadBalancerInterceptor拦截器对RestTemplate的请求进行拦截,并利用Spring Cloud的负载均衡器LoadBalancerClient将以逻辑服务名为host的URI转换成具体的服务实例的过程。同时通过分析LoadBalancerClient的Ribbon实现RibbonLoadBalancerClient,可以知道在使用Ribbon实现负载均衡器的实现,实际使用的还是Ribbon中定义的ILoadBalancer接口的实现,自动化配置会采用ZoneAwareLoadBalancer的实例来实现客户端负载均衡。

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

推荐阅读更多精彩内容