Spring Cloud Ribbon负载均衡策略详解

Spring Cloud Ribbon负载均衡策略详解。

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现,Spring Cloud集成了Netflix Ribbon,只是对Netflix Ribbon进行了一次封装。

这里使用的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扫描。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章