这里使用的ribbon的版本是:ribbon-loadbalancer-2.2.2.jar。
一,IRule接口
IRule接口定义了选择负载均衡策略的基本操作。通过调用choose()方法,就可以选择具体的负载均衡策略。
// 选择目标服务节点
Server choose(Object var1);
// 设置负载均衡策略
void setLoadBalancer(ILoadBalancer var1);
// 获取负载均衡策略
ILoadBalancer getLoadBalancer();
二,ILoadBalancer接口
ILoadBalancer接口定义了ribbon负载均衡的常用操作,有以下几个方法:
void addServers(List<Server> var1);
Server chooseServer(Object var1);
void markServerDown(Server var1);
List<Server> getReachableServers();
List<Server> getAllServers();
三,AbstractLoadBalancerRule抽象类
AbstractLoadBalancerRule实现了IRule接口和IClientConfigAware接口,主要对IRule接口的2个方法进行了简单封装。
private ILoadBalancer ib;
// 设置负载均衡策略
public void setLoadBalancer(ILoadBalancer ib){
this.ib = ib;
}
// 获取负载均衡策略
public ILoadBalancer getLoadBalancer(){
return this.ib;
}
AbstractLoadBalancerRule是每个负载均衡策略需要直接继承的类,Ribbon提供的几个负载均衡策略,都继承了这个抽象类。同理,我们如果需要自定义负载均衡策略,也要继承这个抽象类。
四,AbstractLoadBalancerRule的实现类
AbstractLoadBalancerRule的实现类就是ribbon的具体负载均衡策略,首先来看默认的轮询策略。
1,轮询策略(RoundRobinRule)
轮询策略理解起来比较简单,就是拿到所有的server集合,然后根据id进行遍历。这里的id是ip+端口,Server实体类中定义的id属性如下:
this.id = host + ":" + port
这里还有一点需要注意,轮询策略有一个上限,当轮询了10个服务端节点还没有找到可用服务的话,轮询结束。
2,随机策略(RandomRule)
随机策略:使用jdk自带的随机数生成工具,生成一个随机数,然后去可用服务列表中拉取服务节点Server。如果当前节点不可用,则进入下一轮随机策略,直到选到可用服务节点为止。
这里在while循环中,使用了Thread#interrupted()方法和Thread#yield()方法,使用的很巧妙,可以参考一下。
while(server == null) {
if (Thread.interrupted()) {
return null;
}
……
// 如果随机打到的节点为null,则再次循环随机
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
// 如果节点不是存活状态,则再次循环随机
server = null;
Thread.yield();
}
}
3,可用过滤策略(AvailabilityFilteringRule)
策略描述:过滤掉连接失败的服务节点,并且过滤掉高并发的服务节点,然后从健康的服务节点中,使用轮询策略选出一个节点返回。
AvailabilityFilteringRule#choose()方法实现如下:
// 记录轮询次数,最多轮询10次
int count = 0;
// 轮询10次
for(Server server = roundRobinRule.choose(key); count++ <= 10; server = roundRobinRule.choose(key)) {
if (this.predicate.apply(new PredicateKey(server))) {
return server;
}
}
// 如果轮询10次还没有找到可用server,则执行父类中的筛选逻辑
return super.choose(key);
4,响应时间权重策略
(WeightedResponseTimeRule)
策略描述:根据响应时间,分配一个权重weight,响应时间越长,weight越小,被选中的可能性越低。
如何计算权重呢?代码逻辑位于WeightedResponseTimeRule$$ServerWeight#maintainWeights(),判断逻辑还是挺复杂的,暂时没时间看,先留着,以后有机会再研究。
注意,服务刚启动时,由于统计信息不足,先使用轮询策略。等到信息足够了,切换到WeightedResponseTimeRule策略。
5,轮询失败重试策略(RetryRule)
轮询失败重试策略(RetryRule)是这样工作的,首先使用轮询策略进行负载均衡,如果轮询失败,则再使用轮询策略进行一次重试,相当于重试下一个节点,看下一个节点是否可用,如果再失败,则直接返回失败。
这里还有一个点要注意,重试的时间间隔,默认是500毫秒,我们可以自定义这个重试时间间隔。
this.maxRetryMillis = 500L;
另外,失败重试策略的源码中,RetryRule#choose()方法很有参考价值,如果我们需要自己实现调用接口的失败重试功能的话,可以参考这个方法。这个方法中给我们展示了如何使用Thread#interrupted()和Thread#yield()。
6,并发量最小可用策略(BestAvailableRule)
策略描述:选择一个并发量最小的server返回。如何判断并发量最小呢?ServerStats有个属性activeRequestCount,这个属性记录的就是server的并发量。轮询所有的server,选择其中activeRequestCount最小的那个server,就是并发量最小的服务节点。
接下来我们看源码:
if (this.loadBalancerStats == null) {
return super.choose(key);
} else {
// 获取所有的server
List<Server> serverList = this.getLoadBalancer().getAllServers();
int minimalConcurrentConnections = 2147483647;
long currentTime = System.currentTimeMillis();
Server chosen = null;
Iterator var7 = serverList.iterator();
while(var7.hasNext()) {
Server server = (Server)var7.next();
ServerStats serverStats = this.loadBalancerStats.getSingleServerStats(server);
// 判断断路器是否跳闸,如果没有跳闸,继续往下走。
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
int concurrentConnections = serverStats.getActiveRequestCount(currentTime);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
// 如果没有找到并发量最小的服务节点,则使用父类的策略
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
并发量最小可用策略(BestAvailableRule)的优点是:可以充分考虑每台服务节点的负载,把请求打到负载压力最小的服务节点上。但是缺点是:因为需要轮询所有的服务节点,如果集群数量太大,那么就会比较耗时。当然一般来说,几十台,几百台的集群数量是不用考虑这个问题的。因此对于大部分的项目而言,是一个不错的选择。
7,ZoneAvoidanceRule
策略描述:复合判断server所在区域的性能和server的可用性,来选择server返回。
四,BaseLoadBalancer
BaseLoadBalancer是一个负载均衡器,是ribbon框架提供的负载均衡器。Spring Cloud对ribbon封装以后,直接调用ribbon的负载均衡器来实现微服务客户端的负载均衡。
这里需要注意,ribbon框架本身提供了几个负载均衡器,BaseLoadBalancer只是其中之一。
Spring Cloud是如何封装ribbon框架的呢?Spring Cloud提供了2个接口:ServiceInstanceChooser和LoadBalancerClient,这2个接口就是客户端负载均衡的定义。具体实现类是RibbonLoadBalancerClient。RibbonLoadBalancerClient#choose()方法根据微服务实例的serviceId,然后使用配置的负载均衡策略,打到对于的微服务实例节点上。
OK,到这里,我们简单梳理一下Spring Cloud集成ribbon后,负载均衡的执行逻辑。
1,Spring Cloud RibbonLoadBalancerClient#choose()调用ribbon框架的BaseLoadBalancer。
2,BaseLoadBalancer#chooseServer()选择具体的负载均衡策略(RoundRibonRule),然后执行。
但是,RibbonLoadBalancerClient#choose()是在哪里调用的呢?这里用到了拦截器,@RibbonClient注解自动化配置类LoadBalancerAutoConfiguration.class中有两个注解:
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnClass({LoadBalancerClient.class})
也就是说,在RestTemplate.class和LoadBalancerClient.class存在的情况下,LoadBalancerInterceptor.class会拦截RestTemplate.class上的@LoadBalanced注解,然后将请求中的微服务实例名serviceId转化为具体的ip+端口,然后去请求目标服务节点。
OK,有点乱,我们再来梳理一下调用关系:
1,@LoadBalanced注解
2,org.springframework.web.client.RestTemplate
3,LoadBalancerAutoConfiguration.class
4,LoadBalancerInterceptor.class拦截org.springframework.web.client.RestTemplate的请求,注入客户端负载均衡功能,发送请求到目标服务节点。
这就是Spring Cloud 集成的ribbon客户端负载均衡。
五,如何自定义负载均衡策略
Ribbon不仅实现了几种负载均衡策略,也为开发者提供了自定义负载均衡策略的支持。
自定义负载均衡策略有3个关键点:
1,继承抽象类AbstractLoadBalancerRule
2,自定义的负载均衡策略类,不能放在@ComponentScan所扫描的当前包和子包下。
3,在主启动类上添加@RibbonClient注解,或者在配置文件中指定哪个微服务使用自定义负载均衡策略。
@RibbonClient注解的使用方法如下:
@RibbonClient(name="微服务名称", configuration="自定义配置类.class")
配置文件中配置项如下:
springboot.微服务名称.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
或者
springboot.微服务名称.NFLoadBalancerRuleClassName=com.xxx.xxx.xxx.自定义负载均衡策略实现类
这里需要注意,Spring Cloud微服务启动类上有个注解@SpringBootApplication,这个注解的源码上标注了@ScanComponent注解,也就是说,我们的微服务启动类其实间接引入了@ComponentScan注解。
@ComponentScan注解的作用就是扫描当前包及其子包下的标有指定注解的bean,然后把它们注入到IOC容器。
@Component会扫描哪些注解呢?有4个注解,分别是:
@Component
@Service
@Controller
@Repository
另外,所有间接引入了上面4个注解的注解,最终也会被@ComponentScan扫描,比如我们常用的@Configuration,也会被@ComponentScan扫描。
网友评论