
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 注解的信息

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

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

最终把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启动的时候就会注册这个类。

2.1.2、注册SpringClientFactory
在RibbonAutoConfiguration类中,
会依赖注入 List<RibbonClientSpecification> configurations,这个就是我们@RibbonClients里的配置, 一个@RibbonClient对应着一个RibbonClientSpecification类型的bean,所以这里是个数组,把全部的配置信息都注册进来了。
然后会用@Bean 注册 SpringClientFactory对象,并持有 配置信息 configurations 对象。

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

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注解。

2.2.3、注册拦截器
LoadBalancerAutoConfiguration类下还有加了@Configuration 注解LoadBalancerInterceptorConfig类, 用@bean+方法注册LoadBalancerInterceptor拦截器
比较重要的是,方法参数传入了LoadBalancerClient客户端,并传到构造方法里了,说明拦截器是会持有 LoadBalancerClient客户端对象的。
ribbon调用服务 的逻辑主要在这个拦截器中。

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

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

RestTemplateCustomizer 的方法体具体是咋调用的?
是这样的,LoadBalancerAutoConfiguration类里面还用@Bean 注册了SmartInitializingSingleton接口的匿名实现类的实例,实现afterSingletonsInstantiated()方法。
方法参数注入 刚才注册好的RestTemplateCustomizer 对象, 遍历依赖注入进来的RestTemplate对象列表, 通过 RestTemplateCustomizer对象的customize方法 将拦截器设置到 每一个 RabbitTemplate对象中。

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

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

到这里 所有带有@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方法

doExecute

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

executeInternal,

点到父类AbstractBufferingClientHttpRequest.executeInternal方法

选择InterceptingClientHttpRequest实现类的executeInternal方法

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

这里的拦截器类 如果没有引入重试包的话,那就是LoadBalancerInterceptor类, 如果引入了重试包那就是RetryLoadBalancerInterceptor类,这里我们先讲没有重试的。
进入LoadBalancerInterceptor.intercept 方法,可以看到会调用内部的LoadBalancerClient 负载均衡客户端对象的execute方法。
上面讲过这个LoadBalancerClient 负载均衡客户端对象是 @Bean LoadBalancerInterceptor的时候通过方法参数传到它的构造方法去的。

进到核心方法RibbonLoadBalancerClient.execute方法

3.2.1、取ILoadBalancer 的实例
第一行代码getLoadBalancer 会用 clientFactory对象 ,根据 服务名称获取ILoadBalancer 的实例
clientFactory就是一开始注册的 SpringClientFactory实例, 也是通过@Bean的时候,传到方法参数里,然后传到 负载均衡客户端的 构造方法里的。

调到SpringClientFactory的抽象父类NamedContextFactory.getInstance
3.2.1.1、获取 Spring上下文对象
这里会有 获取 Spring上下文对象的操作

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

第一次调用到这里, 肯定还没有创建, 那么就会创建Spring上下文对象。
这也是为什么Ribbon第一次调用比较慢的原因
new AnnotationConfigApplicationContext()出来之后,先是把 一开始的配置信息register到容器中,register 其实就是把类变成beanDefinition 对象,等待后续实例化。

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

所以自己配置类即使没有@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。

1.2、注册ServerList类
EurekaRibbonClientConfiguration类里 会 用 @Bean的方式 注册 ServerList类实例
可以看到返回的是DomainExtractingServerList类实例,并且 持有了DiscoveryEnabledNIWSServerList对象

DiscoveryEnabledNIWSServerList对象会持有eurekaClientProvider,eurekaClientProvider里面包了EurekaClient实例。
DiscoveryEnabledNIWSServerList 类还有一个获取服务列表的方法obtainServersViaDiscovery()
会根据EurekaClient实例获取服务列表

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

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


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

总结一个DiscoveryEnabledNIWSServerList的obtainServersViaDiscovery()方法 :就是从eurekaClient 客户端对象的本地服务列表里,, 获取对应服务名称下的所有状态为UP的服务实例列表。
什么时候会调用这个方法我们下面再讲。
3、RibbonClientConfiguration
还会register 另外一个配置类org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration 记下来。

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;
}
这里如果不配置,就会由框来 实例化, 通通默认值

2.2、IRule实例
同样的,负载均衡算法 我们也可以自己配
@Bean
public IRule ribbonRule() {
//线性轮训
new RoundRobinRule();
//可以重试的轮训
new RetryRule();
//根据运行情况来计算权重
new WeightedResponseTimeRule();
//过滤掉故障实例,选择请求数最小的实例
new BestAvailableRule();
return new RandomRule();
}
这里如果不配置,就会由框来 实例化,这里默认返回的负载均衡算法实现类 ZoneAvoidanceRule

2.3、IPing实例
同样可以自己配置,
IPing接口 有一个方法 isAlive,是用来判断是否服务是否存活的
PingUrl这个实现类实现的 isAlive方法里 会通过调接口的方法 判断服务是否存活。
@Bean
public IPing iPing() {
//这个实现类会去调用服务来判断服务是否存活
return new PingUrl();
}
如果自己没有配置,那么默认返回的是DummyPing实例

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

也就是 我们不配置 IPing实例的话, 那么调用之前 ,默认就是 不进行检查。
2.4、ILoadBalancer
这个实例存的东西就比较多了, 通过方法参数 注入
IClientConfig实例 ,IRule实例,IPing实例,这几个就是在本类中@Bean注册的(如果我们没配置)
ServerList服务列表对象也会注入, 就是上面将的EurekaRibbonClientConfiguration这个配置类注册的。
最终返回的是 ZoneAwareLoadBalancer 实例。

2.4.1、配置的初始化
构造方法里 会 传入 IClientConfig实例 ,IRule实例,IPing实例,那么在构造方法里,会把这些对象设置 到 ILoadBalancer对象里面去。
看一下构造方法,会进入父类DynamicServerListLoadBalancer的构造方法

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


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

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

有个restOfInit方法,点进去

有两个比较重要的方法 :
1、enableAndInitLearnNewServersFeature() :启动定时器 来刷新 服务列表。

点进PollingServerListUpdater类的start方法
可以看到 会定义一个 Runnable,runable里会调用updateAction成员变量的duUpdate方法。

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


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

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

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

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



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

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

canSkipPing() 是用来判断是否可以 跳过ping服务来检测服务是否存活的
没有IPing实例,或者 是默认的实现类DummyPing

总结 : 完成ILoadBalancer 对象 服务列表的初始化, 并且启动了一个30s执行一次的定时器,从eurekaClient对象里 重新拉取服务列表, 以刷新自己的服务列表。
3.2.1.2、最后调用上下文的refresh方法,启动容器。
refresh方法就会加载Spring容器了, 先是实例化 这几个配置类,然后配置里用@Bean声明的组件也会跟着实例化到Spring容器里。

3.2.1.3、为什么要新建一个Spring上下文对象。
个人觉得是为了和 其他服务的ribbon相关配置 和组件隔离开。
3.2.1.2 从新建的Spring上下文对象返回ILoadBalancer实例
回到我们一开始创建容器,获取ILoadBalancer实例的地方
上面这个新创建的容器已经 实例化了 @Bean出来的组件了。所以Spring容器返回出来的之后,getBean获取 ILoadBalancer 类型的bean,自然就可以获取到了。

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

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

3.2.1.3、根据负载均衡算法选择服务
loadBalancer.chooseServer();

进到ZoneAwareLoadBalancer.chooseServer

如果没有分区的话,那么久会调到父类BaseLoadBalancer的chooseServer方法
通过负载均衡算法 IRule实例.choose(key) 选择服务,

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

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

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


最终拿到 请求的返回值,返回。
这就是ribbon的全部源码了。
网友评论