客户端负载均衡:SpringCloud Netflix Ribbon
系统的负载均衡分为软件负载均衡和硬件负载均衡。软均衡负载分为两种:DNS的负载均衡和IP的负载均衡。
大型网站一般会使用DNS作为第一级负载均衡,基于IP的负载均衡根据请求的IP进行负载均衡。
除了@LoadBalanced之外,Ribbon还提供了@RibbonClient注解,可以为Ribbon客户端声明名称和自定义配置。
声明一个名为say-hello客户端,并且设置它的配置类为RibbonConfiguration,RibbonConfiguration配置类重载了IPing和IRule
@SpringBootApplication
@RestController
@RibbonClient(name="say-hello",configuration = RibbonConfiguration.class)
public class RibbonApplication{
@LoadBalanced
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
@AutoWired
RestTemplate restTemplate;
@RequestMappling("/hi")
public String hi(@RequestParam(value="name",defaultValue="Artaban") String name){
String greeting = this.restTemplate.getForObject("http://say-hello/greeting",String.class);
return String.format("%s,%s!",greeting,name);
}
}
可以通过配置类创建组件实例来覆盖Ribbon提供的默认组件实例。
public class RibbonConfiguration{
@Autowired
IClientConfig ribbonClientConfig;
@Bean
public IPing ribbonPing(IClientConfig config){
return new PingUrl();
}
@Bean
public IRule ribbonRule(IClientConfig config){
return new AvailabilityFilteringRule();
}
}
通过@Bean函数创建了PingUrl实例和AvailabilityFilteringRule实例。替换了默认的NoOpPing实例和ZoneAvoidanceRule实例。
声明了一个名为say-hello的Ribbon客户端,设置它的配置类RibbonConfiguration。
可以在application.yml文件对Ribbon进行配置。服务端列表可以自己设置也可以eureka获取。
源码分析
源码分析流程
配置与实例初始化
@RibbonClient注解会导入RibbonClientConfigurationRegistar类来动态注册Ribbon相关的BeanDefinition。RibbonBeanDefinitionRegistrar是ImportBeanDefinitionRegistar实现类,它的registerBeanDefiniitions方法可以注册Ribbon客户端的配置类。
1,注册Ribbon所需的BeanDefinition
2,Ribbon客户端名称
3,Ribbon客户端的自定义配置,可以配置生成客户端的各个组件,ILoadBalancer,IRule。
获取@RibbonClient的参数数值,获取clientName后进行configuration的注册
Ribbon对于组件实例的管理机制,都是通过NamedContextFactory创建带名称AnnotationConfigApplicationContext子上下文来存储并管理不同的组件实例。
@AutoConfigurationBefore表明该Configuration会在LoadBalancerAutoConfiguration配置类之前进行执行,后者会依赖前者。
在RibbonAutoConfiguration中,被@ConditionalOnMissingBean注解修饰的,意味着Spring容器中,没有被LoadBalancerClient实例,该方法就会初始化RibbonLoadBalancerClient对象。
与OpenFeign的集成
FeignClientFactoryBean是创造FeignClient工厂类,在getObject方法有一个判断,就是当url不为空,就会生成一个具有一个负载均衡的FeignClient。
FeignClientFactoryBean是创造FeignClient的工厂,当请求URL不为空的时候,就会生成一个具有负载均衡的FeignClient。
loadBalance方法会生成LoadBalancerFeignClient实例进行返回。
负载均衡器LoadBalancerClient
maven 包
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-netflix-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
查看源码LoadBalancerClient:
1,从serviceId代表的服务列表选择一个服务器来发送请求
2,构造网络请求URL
loadBalancerClient接口又继承了ServiceInstanceChooser接口,choose方法可以从服务器列表根据负载均衡选择一个服务器实例。serviceId从服务器列表选择一个ServiceInstance
RibbonLoadBalancerClient是LoadBalancerClient实现类。
1,RibbonLoadBalancerClient的execute方法使用ILoadBalancer选择服务器实例
2,将服务器实例封装成RibbonServer对象
3,调用LoadBalancerRequest的apply方法,将参数传递进去进行真正的HTTP请求。
ILoadBalancer
它是定义负载均衡操作的接口,通过SpringClientFactory工厂类的getLoadBalance方法获取。也可以通过ILoadBalancer在RibbonAutoConfiguration被创建的。SpringClientFactory中的实例,都是RibbonAutoConfiguration中被创建的,或者其他自定义配置类。
默认ILoadBalancer实现类ZoneAwareLoadBalancer
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}
ZoneAwareLoadBalancer根据构造函数,看一下与ILoadBalancer相关重要的类。
IClientConfig:Client的配置类
IRule:负载均衡策略
IPing:服务可用性
ServerList:服务列表获取
ServerListFilter:服务器列表的过滤
1,获取当前有关负载均衡的服务器状态的集合
调用ZoneAvoidanceRule的getAvailableZones
方法使用这两个阈值获取所有可用区列表
2,可用的服务区列表
3,从可用的服务区列表选择一个服务区
4,得到zone对应的BaseLoadBalancer
BaseLoadBalancer对象chooseServer方法调用IRule的choose方法。IRule是负载均衡策略的接口。
负载均衡策略实现
IRule是定义Ribbon负载均衡策略的接口。
RoundRobinRule:以RandomRobin方法轮询选择服务器
WeightedResponseTimeRule:响应时间越长,权重越低,权重越低,被选择的可能性越低。
BestAvailableRule:最小请求数的服务器
RetryRule:在选定的负载均衡策略添加重试机制。
ZoneAvoidanceRule:根据服务器所属的服务区的整体运行状况选择。
ZoneAvoidanceRule:是Ribbon默认的IRule的实例。
而ClientConfigEnableRoundRobinRule:常用的IRule子类,常用Round Robin策略。简单轮询。
在其接口IRule下找到其实现类ClientConfigEnabledRoundRobinRule
1,RoundRobinRule用轮询的方式选择不同的服务器,从序号1开始的服务器,知道序号N的服务器。
RibbonClientConfiguration类的ribbonRule方法
2,在配置中配置了Rule就返回。否则使用zoneAvoidanceRule
ZoneAvoidanceRule的类图,它是根据服务器所属的服务区的运行状况和服务器的可用性选择服务器。而PredicateBasedRule是ZoneAvoidanceRule的基类,先使用ILoadBalancer获取服务器列表,再使用AbstractServerPredicate进行服务器的过滤。最后使用轮询策略从剩余的服务器列表选。
PredicateBasedRule接口的getPredicate抽象接口需要子类实现,不同的子类提供不同AbstractServerPredicate实例来实现不同的服务器过滤策略。
ZoneAvoidanceRule使用的是由ZoneAvoidancePredicate和AvailablityPredicate组成的复合策略CompositePredicate。
CompositePredicate一个判断一个服务区的运行状况是否可用,后一个过滤链接过多的服务器。
用Predicate获取一个可用服务器的集合。用轮询算法来选择一个服务器。
如果loadBalancerKey为null,getEligibleServers方法serverOnlyPredicate来依次过滤服务器列表。getEligbleServers方法,调用Predicate的apply方法判断服务器是否可用。它的子类来实现达到不同子类实现不同的过滤策略。
1,如果没有服务区相关的信息,直接返回
2,LoadBalancerStats存储着每个服务器或者节点的执行特征和运行记录。
3,只有一个服务区,直接返回
4,看一下lbStats中记录的服务区列表是否包含当前的服务区,不存在则直接返回
5,当前服务区是否在可用服务区列表中
服务区是多个服务实例的集合
ZoneSnapshot存储了关于服务区的一些运行状况数据。
instanceCount:实例数
loadPerServer: 平均负载
circuitTrippedCount: 断路器断开数量
activeRequestsCount: 活动请求数量
createSnapshot方法其实就是将所有服务区列表转化为哈希表。
getAvailableZones方法是筛选服务区列表的,根据ZoneSnapshot的实例数,实例的平均负载时间和实例故障将不符合ZoneSnapshot从列表删除。