1.内置负载均衡规则
1.1 RoundRobinRule
直接 round robin 轮询,从一堆 server list 中,不断的轮询选择出来一个,每个 server 平摊到的请求,基本是平均的
1.2 AvailabiltyFileringRule
这个会考察服务器的可用性,如果3次链接失败,就会等待30秒后再次访问;如果不断失败,那么等待时间会不断变长,如果某个服务器的并发请求太高了,那么会绕过去,不再访问
1.3 WeightedResponseTimeRule
这个代表权重,每个服务器都可以有权重,权重高的优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问
1.4 ZoneAvoidanceRule
会根据区域和服务器来进行负责均衡,说白了就是机房的意思
1.5 BestAvailableRule
忽略那些连接失败的服务器,然后尽量找并发比较低的服务器来请求
1.6 RandomRule
随机找一个服务器
1.7 RetryRule
可以重试,就是通过 round robin 找到的服务器请求失败,会重新找一个服务器
2.基本整合原理
3.源码级别大体流程
4.@LoadBalanced 极简流程
- 首先通过 @loadBalanced注解,找到对应的相关配置类 LoadBalancerAutoConfiguration ,他还有一个负责异步调用的类 , AsyncLoadBalancerAutoConfiguration
- 在这个类里面,会构建一个空的 restTemplate 的集合,用来存放注解标记的 RestTemplate 实例
- 会在初始化 RestTemplate 的时候,经由 restTemplateCustomizer 对restTemplate 进行定制化,我们这里标记的是普通的 restTemplate ,所以会给所有的 restTemplate 加载一个简单LoadBalancerInterceptor 拦截器
- 在执行 LoadBalancerAutoConfigutaion 进行自动装配的时候,会应为这个注解,先去执行 @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class}),先去执行 RibbonAutoConfigutaion 这个类,初始化信息,主要的就是去初始化 LoadBalancedClient 对象为 RibbonLoadBalancerClient
** **
5.LoadBalancedInterceptor
- 其实这个大体流程和上面那个一样,每次我们用 restTemplate 去发送请求的时候,会首先将这些信息,封装为一个 HttpRequest 对象
- 通过 customizer 增加的拦截器,会调用拦截器的 interceptor 方法
- 从 request 对象中解析出来 uri , 从 uri 中,解析出来服务名,对服务名进行判断(是否为空)
- 后续会调用 requestFactory.createRequest(request, body, execution) ,创建 LoadBalancerRequest
- 将解析出来的服务名和构造出来的 LoadBalancerRequest 交给 LoadBalancerClient 去执行请求
- LoadBalancerClient 是在 spring-cloud-netfilx-core 项目下,在org.springframework.cloud.netflix.ribbon包下,这个项目主要就是一个胶水代码,负责了各个组件之间的整整合,在这里面找到了 RibbonAutoConfiguration 这个类
@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
//上面注解的话就是说明会在 eureka client 之后执行这个
//在 LoadBalancerAutoConfiguration 初始化之前执行这个
// 所以在LoadBalancerAutoConfiguration类中的 LoadBalancerClient 是从下面这个
// 方法进行初始化的,构建了 RibbonLoadBalancerClient 实例对象
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
}
- 所以说基本上就可以说是,restTemplate 的请求,经由@LoadBalanced 注解修饰后,会加入拦截器,最终是交由 RibbonLoadBalancerClient 去负责执行
6.RibbonLoadBalancerClient.execute 执行
6.1 ILoadBalancer loadBalancer = getLoadBalancer(serviceId)
- 首先会通过 getLoadBalancer 方法,从 SpringClientFactory 中获取实例对象
- 会调用父类 NamedContextFactory ,从这个父类的contexts 服务上下文中获取,
- 每个服务名都对应了一个服务上下文对象,用一个 Map 数据结构进行封装。
- 所以会通过 serverName 获取到对应的服务上下文对象,从服务上下文对象中根据 Type 获取对应的 LoadBalancer 实例,我们调用的时候传递的Type是ILoadBalancer,所以最后返回的是 ILoadBalancer 接口
- 那么这个接口是什么类型的呢,我们可以在 RibbonClientConfiguration 这个类中发现,ILoadBalancer 初始化的是 ZoneAwareLoadBalancer 这个对象,他的父类是 DynamicServerListLoadBalancer ,它的父类是 BaseLoadBalancer 继续往上是 AbstractLoadBalancer 在往上会发现implements ILoadBalancer ,所以这里可以是返回的是 ILoadBalancer接口,但类型确是ZoneAwareLoadBalancer
** **
6.2 ZoneAwareLoadBalancer 获取注册表
一个调用请求过来的话,会首先被拦截器拦截,执行拦截器的 interrupt 方法,获取到 url 并从 url 中解析出来ServiceName ,交由 RibbonLoadBalancerClient 进行执行,调用的是 execute 方法。
首先会通过服务名获取到服务的上下文对象,通过 IloadBalancer.class 获取到对应的 ILoadBalancer 接口,但这个接口的话,会在 RibbonClientConfiguration 类中,通过 @Bean 进行初始化,所以 ILoadBalancer 对应的实现类是 ZoneAwareLoadBalancer 实例。
在构建 ZoneAwareLoadBalancer 的时候,会调用 restOfInit 方法,这个方法中调用的updateListOfServers方法就是获取注册表的方法,这个方法主要是靠 ServerImpl 去执行而Server是在创建 ZoneAwareLoadBalancer 实例的时候从构造方法中传入的,我们可以从EurekaRibbonClientConfiguration 类中发现会对 ServerList 进行构造,会创建 DiscoveryEnabledNIWSServerList 实例,并将其交由DomainExtractingServerList ,也就是说,最后 ServerList 接口的实现类就是 DomainExtractingServerList 这个类。
那么通过 ServerImpl 去进行调用,也就是相当于调用 DomainExtractingServerList 的getInitialListOfServers 方法,由于在创建的时候,其实是将 DiscoveryEnabledNIWSServerList 交给它管理,所以最后的实现方法是 **DiscoveryEnabledNIWSServerList.**obtainServersViaDiscovery 。
这里的主要逻辑就是获取到 EurekaClient 实例,通过服务名获取到对应的服务实例,将服务实例的地址,放到一个服务实例集合中,当然中间会干一些比较琐碎的事情,但大体流程的话就是这样:通过构建 ZoneAwareLoadBalance 的时候通过已经初始化好的 ServerLIist 的实现DiscoveryEnabledNIWSServerList 来负责和 eureka cient 进行交互,通过服务名获取到对应的服务信息,将服务实例地址加入到集合中,供后面使用。
那么在我们的实际使用中,eureka 的信息是会改变的,因此会在调用 restOfInit 方法的时候,调用enableAndInitLearnNewServersFeature 它内部的话,主要还是调用上面的那个获取注册表的方法,同时初始化了一个延迟一秒每三十秒调用一次的调度任务,每隔三十秒更新一次注册表信息。
6.3 通过算法得到服务地址
首先会通过 getServer() 方法去调用 BaseLoadBalancer.chooseServer 的方法,通过 IRule 实例,进行规则的选择,默认实例是 PredicateBasedRule ,核心执行的话是在 AbstractServerPredicate 类中,调用的 incermentAndGetModulo 方法,内部算法也比较简单,主要就是用当前值+1 和 服务数量进行取模,并且将当前值修改为计算出来的值
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
7. Ping 机制
原生的 Ribbon 的话有一个 ping 机制,就是有一个 IPing 的组件,会时不时的 ping 一下服务器,看看服务器是否存活,这样我们就可以只对存活的进行访问。
默认的话,在创建 ZoneAwareLoadBalancer 的时候,会传入一个 IPing 组件,它是在 org.springframework.cloud.netflix.ribbon.eureka 包下的EurekaRibbonClientConfiguration 类中进行的初始化,主要就是创建了一个 NIWSDiscoveryPing 对象。
NIWSDiscoveryPing 这个 Ping 进行校验的方法很简单,就是获取到每个服务对应的 InstanceInfo 实例对象,判断这个实例对象的状态是不是 up 状态。
在这检查的方法调用的话,主要是在创建 ZoneAwareLoadBalancer 的时候,通过调用父类 BaseLoadBalancer 的 initWithConfig 方法,初始化了一个 PingTask 的调度任务,会每隔30秒调用一次,主要就是获取到服务实例的集合,遍历这个集合,都调用一下NIWSDiscoveryPing的isAlive方法,判断是否存活(也就是判断当前服务实例对应的状态是不是 up)