Spring Cloud-RIBBON负载均衡算法解析

Ribbon 负载均衡算法 IRule
Ribbon 提供了多种负载均衡策略算法,例如轮询算法、随机算法、响应时间加权算法等。

默认采用的是轮询算法。当然,我们也可以指定 Ribbon 所要采用的负载均衡算法。

public interface IRule{
    /*
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */

    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

(1) choose()方法

Ribbon 的负载均衡算法需要实现 IRule 接口,而该接口中的核心方法即 choose()方法, 即对提供者的选择方式就是在该方法中体现的。

查看该方法的注释,其意思是“根据 key 的值从 allServers 或 upServers 集合中选择一个 可用的 Server”。allServers 是所有提供者集合,而 upServers 则是所有可用的提供者集合。 choose()方法的参数 key 是集合选择的标准。

(2) loadBalancer 是 ILoadBalance 接口对象,跟踪 ILoadBalancer 接口实现类 BaseLoadBalance 的 choose()方法。

Ribbon 自带算法

Ribbon 的内置可用负载均衡算法有七种。

  • RoundRobinRule

        轮询策略。Ribbon 默认采用的策略。

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }
        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            int nextServerIndex = incrementAndGetModulo(serverCount);//自旋累加
            server = allServers.get(nextServerIndex);
            //服务端状态判定
            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }
            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer:     
               "+ lb);
        }
        return server;
    }
  • BestAvailableRule

   选择并发量最小的 provider,即连接的消费者数量最少的 provider。其会遍历服务列表 中的每一个 provider,选择当前连接数量        minimalConcurrentConnections 最小的 provider。

public Server choose(Object key) {
        if (loadBalancerStats == null) {
            return super.choose(key);
        }
        List<Server> serverList = getLoadBalancer().getAllServers();
        int minimalConcurrentConnections = Integer.MAX_VALUE;
        long currentTime = System.currentTimeMillis();
        Server chosen = null;
        for (Server server: serverList) {
            ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
            if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);//获取请求数量
//获取最少的
                if (concurrentConnections < minimalConcurrentConnections) {
                    minimalConcurrentConnections = concurrentConnections;
                    chosen = server;
                }
            }
        }
        if (chosen == null) {
            return super.choose(key);
        } else {
            return chosen;
        }
    }
  • AvailabilityFilteringRule

该算法规则是:过滤掉由于连续连接或读故障而处于断路器跳闸状态的 provider,或已经超过连接极限的 provider,对剩余 provider 采用轮询策略。

public Server choose(Object key) {
        int count = 0;
        //先通过轮询获取一个
        Server server = roundRobinRule.choose(key);
        while (count++ <= 10) {
//去除问题server
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        return super.choose(key);
    }
  • PredicateBasedRule

     复合判断 provider 所在区域的性能及 provider 的可用性选择服务器。

public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
  • RandomRule

      随机策略,从所有可用的 provider 中随机选择一个。

    are in circuit breaker(断路器) tripped(跳闸) state due to(由于) consecutive(连续的) connection or read failures, or have active connections that exceeds(超出) a configurable limit(default is Integer.MAX_VALUE).

 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();
            if (serverCount == 0) {
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            }

            int index = chooseRandomInt(serverCount);
            server = upList.get(index);

            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }

        return server;

    }
  •  RetryRule

    先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认 的时限为 500 毫秒。

public Server choose(ILoadBalancer lb, Object key) {
		long requestTime = System.currentTimeMillis();
		long deadline = requestTime + maxRetryMillis;//500ms

		Server answer = null;

		answer = subRule.choose(key);

		if (((answer == null) || (!answer.isAlive()))
				&& (System.currentTimeMillis() < deadline)) {

			InterruptTask task = new InterruptTask(deadline
					- System.currentTimeMillis());

			while (!Thread.interrupted()) {
				answer = subRule.choose(key);

				if (((answer == null) || (!answer.isAlive()))
						&& (System.currentTimeMillis() < deadline)) {
					/* pause and retry hoping it's transient */
					Thread.yield();
				} else {
					break;
				}
			}

			task.cancel();
		}

		if ((answer == null) || (!answer.isAlive())) {
			return null;
		} else {
			return answer;
		}
	}
  • ClientConfigEnabledRoundRobinRule
public Server choose(Object key) {
        if (roundRobinRule != null) {
            return roundRobinRule.choose(key);
        } else {
            throw new IllegalArgumentException(
                    "This class has not been initialized with the RoundRobinRule class");
        }
    }

轮询为null则报异常

  • WeightedResponseTimeRule

根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule;

 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    @Override
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            // get hold of the current reference in case it is changed from the other thread
            List<Double> currentWeights = accumulatedWeights;
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();

            if (serverCount == 0) {
                return null;
            }

            int serverIndex = 0;

            // last one in the list is the sum of all weights
            double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1); 
            // No server has been hit yet and total weight is not initialized
            // fallback to use round robin
            if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {
                server =  super.choose(getLoadBalancer(), key);
                if(server == null) {
                    return server;
                }
            } else {
                // generate a random weight between 0 (inclusive) to maxTotalWeight (exclusive)
                double randomWeight = random.nextDouble() * maxTotalWeight;
                // pick the server index based on the randomIndex
                int n = 0;
                for (Double d : currentWeights) {
                    if (d >= randomWeight) {
                        serverIndex = n;
                        break;
                    } else {
                        n++;
                    }
                }

                server = allList.get(serverIndex);
            }

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Next.
            server = null;
        }
        return server;
    }

  更换默认策略

Ribbon 默认采用的是 RoundRobinRule,即轮询策略。但通过修改消费者的启动类,或
CodeConfig 类可以实现更换负载均衡策略的目的:只需添加如下代码即可。

@EnableFeignClients(basePackages = "xxxxxxx")
@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public IRule loadBalanceRule() {
        // 指定要排除的Server的端口号
      return new ZoneAvoidanceRule();
    }

}

自定义算法

public class CustomRule implements IRule {
    private ILoadBalancer lb;
    // 要排除的提供者端口号集合
    private List<Integer> excludePorts;

    public CustomRule() {
    }

    public CustomRule(List<Integer> excludePorts) {
        this.excludePorts = excludePorts;
    }

    @Override
    public Server choose(Object key) {
        // 获取所有可用的提供者
        List<Server> servers = lb.getReachableServers();
        // 获取所有排除了指定端口号的提供者
        List<Server> availableServers = this.getAvailableServers(servers);
        // 从剩余的提供者中随机获取可用的提供者
        return this.getAvailableRandomServers(availableServers);
    }

    // 获取所有排除了指定端口号的提供者
    private List<Server> getAvailableServers(List<Server> servers) {
        // 若不存在要排除的Server,则直接将所有可用Servers返回
        if(excludePorts == null || excludePorts.size() == 0) {
            return servers;
        }

        // 定义一个集合,用于存放排除了指定端口号的Server
        List<Server> aservers = new ArrayList<>();
        for (Server server : servers) {
            boolean flag = true;
            for(Integer port : excludePorts) {
                if(server.getPort() == port) {
                    flag = false;
                    break;
                }
            }
            // 若flag为false,说明上面的for循环执行了break,说明当前遍历的Server是要排除掉的Server
            if (flag) {
                aservers.add(server);
            }
        }
        return aservers;
    }

    // 从剩余的提供者中随机获取可用的提供者
    private Server getAvailableRandomServers(List<Server> availableServers) {
        // 获取一个[0,availableServers.size())的随机整数
        int index = new Random().nextInt(availableServers.size());
        return availableServers.get(index);
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        this.lb = lb;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
        return lb;
    }
}

 

修改启动类

@EnableFeignClients(basePackages = "xxxxxxx")
@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public IRule loadBalanceRule() {
        // 指定要排除的Server的端口号
        List<Integer> ports = new ArrayList<>();
        ports.add(8082);
        return new CustomRule(ports);
    }

}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值