17. SpringCloud之史上最详细Ribbon源码解读!

image.png

1、配置信息解析

1.1、使用方式

在使用ribbon的时候,会用 @RibbonClients 对ribbon进行配置

@RibbonClients 里配置多个 RibbonClient, 每个RibbonClient对应的 利用ribbon进行负载均衡的 服务名(可能有多个实例), configuration的值是一个配置类

@Configuration
@RibbonClients(value = {
        @RibbonClient(name = "micro-order",configuration = RibbonLoadBalanceMicroOrderConfig.class)
})
public class LoadBalanceConfig {
}

RibbonLoadBalanceMicroOrderConfig

@Configuration
public class RibbonLoadBalanceMicroOrderConfig {

    //    @RibbonClientName
    private String name = "micro-order";

    @Bean
    @ConditionalOnClass
    public IClientConfig defaultClientConfigImpl() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(name);
        config.set(CommonClientConfigKey.MaxAutoRetries, 2);
        config.set(CommonClientConfigKey.MaxAutoRetriesNextServer, 2);
        config.set(CommonClientConfigKey.ConnectTimeout, 2000);
        config.set(CommonClientConfigKey.ReadTimeout, 4000);
        config.set(CommonClientConfigKey.OkToRetryOnAllOperations, true);
        return config;
    }
    /*
     * 判断服务是否存活
     * 不建议使用
     * */
//    @Bean
//    public IPing iPing() {
//        //这个实现类会去调用服务来判断服务是否存活
//        return new PingUrl();
//    }

    @Bean
    public IRule ribbonRule() {
        //线性轮训
        new RoundRobinRule();
        //可以重试的轮训
        new RetryRule();
        //根据运行情况来计算权重
        new WeightedResponseTimeRule();
        //过滤掉故障实例,选择请求数最小的实例
        new BestAvailableRule();
        return new RandomRule();
    }
}

1.2、配置解析源码

@RibbonClients注解源码

会用@Import 注解 导入 RibbonClientConfigurationRegistrar这个类

@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {

   RibbonClient[] value() default {};

   Class[] defaultConfiguration() default {};

}

而 RibbonClientConfigurationRegistrar类 实现 ImportBeanDefinitionRegistrar 接口, 实现 registerBeanDefinitions方法,。

Spring解析@Import(RibbonClientConfigurationRegistrar.class)的时候, 会优先把RibbonClientConfigurationRegistrar类实例化,并且调用 其实现自ImportBeanDefinitionRegistrar 接口的registerBeanDefinitions() ,并传入BeanDefinitionRegistry对象 和 导入它的那个类的 metadata 元数据对象(就是我们自己写的有@RibbonClients的类)。

metadata 元数据对象主要是为了 获取 @RibbonClients 注解里的配置信息。

配置的解析工作就在 registerBeanDefinitions方法里。

先从 metadata 元数据对象 获取@RibbonClients 注解的信息

image.png

遍历@RibbonClients 注解里的 每一个 @RibbonClient,

image.png

创建一个RibbonClientSpecification类的BeanDefinition对象,并把 @RibbonClient里的name(服务名称) 和 configuration类名 作为RibbonClientSpecification类实例化bean的构造方法的参数 设置进去。

image.png

最终把beanDefinition注册到Spring容器中,等待实例化。最后 RibbonClientSpecification类实例化出来的bean的name属性 = micro-order,configuration = 我们的配置类。

@RibbonClients 配置注解 里的每一个 @RibbonClient都对应 Spring容器里的类型为RibbonClientSpecification 的 bean。

2、重要组件实例化

2.1、RibbonAutoConfiguration类

2.1.1、SPI入口

spi导入bean

org.springframework.cloud:spring-cloud-netflix-ribbon:2.0.0.RELEASE2包下META-INF路径下的spring.factories 会配置 导入RibbonAutoConfiguration类。那么Spring启动的时候就会注册这个类。

image.png

2.1.2、注册SpringClientFactory

在RibbonAutoConfiguration类中,

会依赖注入 List configurations,这个就是我们@RibbonClients里的配置, 一个@RibbonClient对应着一个RibbonClientSpecification类型的bean,所以这里是个数组,把全部的配置信息都注册进来了。

然后会用@Bean 注册 SpringClientFactory对象,并持有 配置信息 configurations 对象。

image.png

2.1.3、注册 LoadBalancerClient

用@Bean 注册 LoadBalancerClient 对象, 并持有SpringClientFactory对象。

image.png

2.2、LoadBalancerAutoConfiguration类

2.2.1、SPI入口

org.springframework.cloud:spring-cloud-commons:2.0.0.RELEASE2包下META-INF路径下的spring.factories 会配置 导入LoadBalancerAutoConfiguration类,那么Spring启动的时候就会注册这个类。

2.2.2、依赖注入RestTemplate对象

这里只有 @Bean注册实例时加了@LoadBalanced注解的 RestTemplate对象的 方才能依赖注入到源码中的,如果不加这个注解则没有 ribbon 功能,因为源码里面获取不到这个实例了

因为 @LoadBalanced 里有个 @Qualifier注解。

image.png

2.2.3、注册拦截器

LoadBalancerAutoConfiguration类下还有加了@Configuration 注解LoadBalancerInterceptorConfig类, 用@bean+方法注册LoadBalancerInterceptor拦截器

比较重要的是,方法参数传入了LoadBalancerClient客户端,并传到构造方法里了,说明拦截器是会持有 LoadBalancerClient客户端对象的。

ribbon调用服务 的逻辑主要在这个拦截器中。

image.png

2.2.4、将拦截器设置到 RestTemplate中

先注册一个 RestTemplateCustomizer 的匿名实现类对象, 方法体 就是 把loadBalancerInterceptor 拦截器 set到 RestTemplate对象中的interceptors 集合中。

image.png

interceptors集合是 RestTemplate 继承至父类 InterceptingHttpAccessor 的成员变量。

image.png

RestTemplateCustomizer 的方法体具体是咋调用的?

是这样的,LoadBalancerAutoConfiguration类里面还用@Bean 注册了SmartInitializingSingleton接口的匿名实现类的实例,实现afterSingletonsInstantiated()方法。

方法参数注入 刚才注册好的RestTemplateCustomizer 对象, 遍历依赖注入进来的RestTemplate对象列表, 通过 RestTemplateCustomizer对象的customize方法 将拦截器设置到 每一个 RabbitTemplate对象中。

image.png

SmartInitializingSingleton接口只有一个方法,所有这个匿名实现类在这里可以写成lambda。

image.png

SmartInitializingSingleton接口类型的所有实例的afterSingletonsInstantiated()方法被调用 是在 Spring实例化完所有bean之后。

image.png

到这里 所有带有@LoadBalance注解的 restTemplate对象 里面都有ribbon负载均衡拦截器了,并且持有配置信息的负载均衡客户端LoadBalancerClient 对象也被注册到了Spring容器中。 那么 接下来,restTemplate在发起远程调用的时候, 会被拦截器拦截,再利用 负载均衡客户端进行 真正的负载均衡。

3、ribbon调用源码

3.1、测试代码

注册一个有@LoadBalanced 注解的RestTemplate的对象,然后用它对服务micro-order的/order/list接口发起调用,看看源码的执行流程。

@Autowired
private RestTemplate restTemplate;

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


@GetMapping("/orderList")
public String getOrderList(String username) {
    return restTemplate.getForObject("http://" + "micro-order" + "/order/list?username=" + username+"&i=1", String.class);
}

3.2、源码执行流程

点进restTemplate.getForObject 方法,点进execute方法

image.png

doExecute

image.png

创建一个Request对象,调execute方法

image.png

executeInternal,

image.png

点到父类AbstractBufferingClientHttpRequest.executeInternal方法

image.png

选择InterceptingClientHttpRequest实现类的executeInternal方法

image.png

InterceptingClientHttpRequest 内部会有个InterceptingRequestExecution对象, 还会有我们的拦截器组件, 调他的execute方法, execute方法会迭代拦截器, 并传入自己(request对象),作为回调,进行 负载均衡之后,再回调request对象发请求。

image.png

这里的拦截器类 如果没有引入重试包的话,那就是LoadBalancerInterceptor类, 如果引入了重试包那就是RetryLoadBalancerInterceptor类,这里我们先讲没有重试的。

进入LoadBalancerInterceptor.intercept 方法,可以看到会调用内部的LoadBalancerClient 负载均衡客户端对象的execute方法。

上面讲过这个LoadBalancerClient 负载均衡客户端对象是 @Bean LoadBalancerInterceptor的时候通过方法参数传到它的构造方法去的。

image.png

进到核心方法RibbonLoadBalancerClient.execute方法

image.png

3.2.1、取ILoadBalancer 的实例

第一行代码getLoadBalancer 会用 clientFactory对象 ,根据 服务名称获取ILoadBalancer 的实例

clientFactory就是一开始注册的 SpringClientFactory实例, 也是通过@Bean的时候,传到方法参数里,然后传到 负载均衡客户端的 构造方法里的。

image.png

调到SpringClientFactory的抽象父类NamedContextFactory.getInstance

3.2.1.1、获取 Spring上下文对象

这里会有 获取 Spring上下文对象的操作

image.png

点进getContext(name) 会发现有一层缓存(毕竟Spring上下文对象很重), 缓存的结构 是{服务名称 : ApplicationContext对象}, 也就是说,在ribbon调用的时候, 一个服务名称 就对应了 一个 ApplicationContext上下文对象。

image.png

第一次调用到这里, 肯定还没有创建, 那么就会创建Spring上下文对象。

这也是为什么Ribbon第一次调用比较慢的原因

new AnnotationConfigApplicationContext()出来之后,先是把 一开始的配置信息register到容器中,register 其实就是把类变成beanDefinition 对象,等待后续实例化。

image.png
1、注册自己的配置类

从configurations 里 根据服务名取出, name = 'micor-order'的 就是我们自己配置的配置类会被注册、

image.png

所以自己配置类即使没有@Configuration,类里面用@Bean 注册的 IClientConfig,IRule等 bean也会被注册。

因为我们通过@RibbonClients 指定了这个类了。

public class RibbonLoadBalanceMicroOrderConfig {

    //    @RibbonClientName
    private String name = "micro-order";

    @Bean
    @ConditionalOnClass
    public IClientConfig defaultClientConfigImpl() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(name);
//        config.set(CommonClientConfigKey.MaxAutoRetries, 2);
//        config.set(CommonClientConfigKey.MaxAutoRetriesNextServer, 2);
        config.set(CommonClientConfigKey.ConnectTimeout, 2000);
        config.set(CommonClientConfigKey.ReadTimeout, 4000);
//        config.set(CommonClientConfigKey.OkToRetryOnAllOperations, true);
        return config;
    }
    /*
     * 判断服务是否存活
     * 不建议使用
     * */
//    @Bean
//    public IPing iPing() {
//        //这个实现类会去调用服务来判断服务是否存活
//        return new PingUrl();
//    }

    @Bean
    public IRule ribbonRule() {
        //线性轮训
        new RoundRobinRule();
        //可以重试的轮训
        new RetryRule();
        //根据运行情况来计算权重
        new WeightedResponseTimeRule();
        //过滤掉故障实例,选择请求数最小的实例
        new BestAvailableRule();
        return new RandomRule();
    }
}
2、注册EurekaRibbonClientConfiguration

configurations 里面name 以 "default."开头的就是 自带的配置类、

这里有个比较重要的配置类org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration。

image.png
1.2、注册ServerList类

EurekaRibbonClientConfiguration类里 会 用 @Bean的方式 注册 ServerList类实例

可以看到返回的是DomainExtractingServerList类实例,并且 持有了DiscoveryEnabledNIWSServerList对象

image.png

DiscoveryEnabledNIWSServerList对象会持有eurekaClientProvider,eurekaClientProvider里面包了EurekaClient实例。

DiscoveryEnabledNIWSServerList 类还有一个获取服务列表的方法obtainServersViaDiscovery()

会根据EurekaClient实例获取服务列表

image.png

eurekaClient.getInstancesByVipAddress

会从localRegionApps 里返回服务列表, eurekaClient的这个变量 就是eureka客户端从 eureka服务端拉取的 全部服务列表, 都存在这里。 最后赋值给applications对象

image.png

然后调用 applications对象的getInstancesByVirtualHostName方法,根据服务名称来服务对应的服务列表,

image.png
image.png

拿到服务名对应的 所有的服务列表之后,过滤一下 UP状态的,存到 返回出去。

image.png

总结一个DiscoveryEnabledNIWSServerList的obtainServersViaDiscovery()方法 :就是从eurekaClient 客户端对象的本地服务列表里,, 获取对应服务名称下的所有状态为UP的服务实例列表。

什么时候会调用这个方法我们下面再讲。

3、RibbonClientConfiguration

还会register 另外一个配置类org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration 记下来。

image.png

RibbonClientConfiguration类会注册几个比较重要的组件 :

  • IClientConfig实例 : ribbot客户端配置
  • IRule实例 :负载均衡算法
  • IPing实例 : 通过ping 判断服务是否存活
  • ILoadBalancer实例 :这个就是我们一开始获取的那个,封装了一些负载均衡调用的方法
2.1、IClientConfig实例

这个就是客户端的配置,比如我们自己配的

    @Bean
    @ConditionalOnClass
    public IClientConfig defaultClientConfigImpl() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(name);
//        config.set(CommonClientConfigKey.MaxAutoRetries, 2);
//        config.set(CommonClientConfigKey.MaxAutoRetriesNextServer, 2);
        config.set(CommonClientConfigKey.ConnectTimeout, 2000);
        config.set(CommonClientConfigKey.ReadTimeout, 4000);
//        config.set(CommonClientConfigKey.OkToRetryOnAllOperations, true);
        return config;
    }

这里如果不配置,就会由框来 实例化, 通通默认值

image.png
2.2、IRule实例

同样的,负载均衡算法 我们也可以自己配

@Bean
public IRule ribbonRule() {
    //线性轮训
    new RoundRobinRule();
    //可以重试的轮训
    new RetryRule();
    //根据运行情况来计算权重
    new WeightedResponseTimeRule();
    //过滤掉故障实例,选择请求数最小的实例
    new BestAvailableRule();
    return new RandomRule();
}

这里如果不配置,就会由框来 实例化,这里默认返回的负载均衡算法实现类 ZoneAvoidanceRule

image.png
2.3、IPing实例

同样可以自己配置,

IPing接口 有一个方法 isAlive,是用来判断是否服务是否存活的

PingUrl这个实现类实现的 isAlive方法里 会通过调接口的方法 判断服务是否存活。

@Bean
public IPing iPing() {
    //这个实现类会去调用服务来判断服务是否存活
    return new PingUrl();
}

如果自己没有配置,那么默认返回的是DummyPing实例

image.png

DummyPing 就是 不进行检查,他的alive方法 恒返回true。

image.png

也就是 我们不配置 IPing实例的话, 那么调用之前 ,默认就是 不进行检查。

2.4、ILoadBalancer

这个实例存的东西就比较多了, 通过方法参数 注入

IClientConfig实例 ,IRule实例,IPing实例,这几个就是在本类中@Bean注册的(如果我们没配置)

ServerList服务列表对象也会注入, 就是上面将的EurekaRibbonClientConfiguration这个配置类注册的。

最终返回的是 ZoneAwareLoadBalancer 实例。

image.png
2.4.1、配置的初始化

构造方法里 会 传入 IClientConfig实例 ,IRule实例,IPing实例,那么在构造方法里,会把这些对象设置 到 ILoadBalancer对象里面去。

看一下构造方法,会进入父类DynamicServerListLoadBalancer的构造方法

image.png

再进入 父类的父类BaseLoadBalancer 的构造方法

image.png
image.png

initWithConfig 方法 就会进行 客户单配置clientConfig, 负载均衡IRule 对象,IPing对象的赋值。统统设置到LoadBalancer的成员变量中。

image.png
2.4.2、获取服务列表

值得注意的是, ZoneAwareLoadBalancer的构造函数里会调用 上面讲到的ServerList对象里DiscoveryEnabledNIWSServerList对象 ,调用它的obtainServersViaDiscovery()方法去获取服务列表。

image.png

有个restOfInit方法,点进去

image.png

有两个比较重要的方法 :

1、enableAndInitLearnNewServersFeature() :启动定时器 来刷新 服务列表。

image.png
点进PollingServerListUpdater类的start方法

可以看到 会定义一个 Runnable,runable里会调用updateAction成员变量的duUpdate方法。
image.png

定时器初始延迟时间是写死的,1秒钟之后执行, 多少时间执行一次也可以自己通过IClientConfig配置,如果没配置,默认就是30s, 都是通过 构造方法赋好值的

image.png
image.png

updateAction成员变量的doUpdate方法,就是调用updateListOfServers()。就是每30s刷新一次服务列表。

image.png
  1. updateListOfServers() :初始化服务列表。

这个是在 初始化的时候,先获取一遍服务列表。和上面定时器调的是同一个方法。

image.png

serverListImpl对象里就是DiscoveryEnabledNIWSServerList对象了,那么最终调到了获取服务列表的代码里。

image.png

返回出服务列表之后, 赋给本地局部变量servers,然后更新到成员变量里,存储起来

image.png
image.png
image.png

遍历每一个服务实例对象,加到servers容器中

image.png

最终赋给allServerList,upServerList 这两个成员变量。

image.png

canSkipPing() 是用来判断是否可以 跳过ping服务来检测服务是否存活的

没有IPing实例,或者 是默认的实现类DummyPing

image.png

总结 : 完成ILoadBalancer 对象 服务列表的初始化, 并且启动了一个30s执行一次的定时器,从eurekaClient对象里 重新拉取服务列表, 以刷新自己的服务列表。

3.2.1.2、最后调用上下文的refresh方法,启动容器。

refresh方法就会加载Spring容器了, 先是实例化 这几个配置类,然后配置里用@Bean声明的组件也会跟着实例化到Spring容器里。

image.png

3.2.1.3、为什么要新建一个Spring上下文对象。

个人觉得是为了和 其他服务的ribbon相关配置 和组件隔离开。

3.2.1.2 从新建的Spring上下文对象返回ILoadBalancer实例

回到我们一开始创建容器,获取ILoadBalancer实例的地方

上面这个新创建的容器已经 实例化了 @Bean出来的组件了。所以Spring容器返回出来的之后,getBean获取 ILoadBalancer 类型的bean,自然就可以获取到了。

image.png

类型就是就是默认返回的ZoneAwareLoadBalancer类型。

image.png

接下来代码就要用 ILoadBalancer实例根据负载均衡算法 去选择 一个服务了。

image.png

3.2.1.3、根据负载均衡算法选择服务

loadBalancer.chooseServer();

image.png

进到ZoneAwareLoadBalancer.chooseServer

image.png

如果没有分区的话,那么久会调到父类BaseLoadBalancer的chooseServer方法

通过负载均衡算法 IRule实例.choose(key) 选择服务,

image.png

由于我们代码配置的是随机负载均衡算法, RandomRule,所以进入RandomRule.choose()

image.png

接下来就是 负载均衡算法的具体实现了。

会 生成 0 到 全部服务列表的总数 之间的随机数,然后当做下标,从 刷新过后的服务列表容器upServerList取出来。 最终返回的就是这个具体的服务。

image.png

3.2.1.4、调用该服务

取到了某一个服务之后, 包到RibbonServer对象里 ,开始调用

image.png
image.png

最终拿到 请求的返回值,返回。

这就是ribbon的全部源码了。

你可能感兴趣的:(17. SpringCloud之史上最详细Ribbon源码解读!)