Spring Cloud Ribbon负载均衡策略详解

        通过之前的文章可以知道, Ribbon负载均衡器选择服务实例的方式是通过“选择策略”实现的, Ribbon实现了很多种选择策略,UML静态类图如上图。 IRule是负载均衡的策略接口,表示某一种规则作为负载均衡的算法,负载平衡策略包括 循环,基于响应时间等。 AbstractLoadBalancerRule 提供获取负载均衡器ILoadBalancer 的默认实现,所有负载策略都继承AbstractLoadBalancerRule。

1 RoundRobinRule策略规则

RoundRobinRule策略实现按照线性轮询的方式依次选择每个服务实例的功能,这也是Ribbon的默认使用策略, 该策略的工作原理

  • 是声明一个计数器AtomicInteger
  • 每次获取所有线上和线下的所有的服务实力列表集合List
  • 每次执行策略的时候计数器数量+1,并根据总服务实例数量取模,得到轮询的服务实例编号,算法是(当前数+1)%serverCount,如果有2个服务第一次(0+1)%2=1,第二次(1+1)%2=0
  • 通过服务实例的编号当作List集合的下标,从List里获取服务
/**
*
* 方便阅读,省略了部分不重要的代码
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;//原子操作的计数器
    
    public Server choose(ILoadBalancer lb, Object key) {
        //。。。略
        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {

            //获取线上和线下的所有服务实例
            List<Server> allServers = lb.getAllServers();

            int serverCount = allServers.size();
        
            //nextServerCyclicCounter计数器+1并通过取模的方式得到序号
            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            //。。。略
            //如果服务在线上的可用的则使用
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        //。。。略
        return server;
    }

    //其他代码省略
}

2 WeightedResponseTimeRule策略规则

WeightedResponseTimeRule策略是对RoundRobinRule的扩展,根据实例运行情况的权重来挑选实例,以达到更优的分配效果,这是一种使用平均/百分比响应时间为每个服务器分配动态“权重”的规则,这种处理规则的原理如下

1. 启动一个30秒执行的定时任务,用来为每个服务实力计算全重,先根据每个实例的平均响应时间得到总平均响应时间,然后为负载均衡器中维护的实例依次计算权重。

假设 A(t=10)、B(t=30)、C(t=40),D(t=20) ,总时间=100

A的权重=100-10=90

B的权重=90+(100-30)=160

C的权重=160+(100-40)=220

D的权重=220+(100-20)=300

2. 分配的时候随即生成1个数字,按照数字和全重 确认发送给哪一个客户端,如下

假设4个端点:A(wt=90)、B(wt=160)、C(wt=220),D(wt=300)。使用Random API,生成一个介于1和最大权重(90+160+220+300)之间的随机数。

基于权重,我们间隔时间如下:

1---90(A的重量) ,如果随机数是1---90则将请求发送给A

91---250(A的重量+B的重量) ,如果随机数是91---250则将请求发送给B

251---470(A的重量+B的重量+C的重量)  如果随机数是251---470 则将请求发送给C

471---770(A的重量+B的重量+C的重量+D的重量) ,如果生成的随机数是471---770,则将请求发送给D

//为方便阅读删除部分代码
public class WeightedResponseTimeRule extends RoundRobinRule {

    public Server choose(ILoadBalancer lb, Object key) {
            // last one in the list is the sum of all weights
            // 最后1个是权重总和
            double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1); 

            // 如果权重值小于0.001,则采用父类线性轮询均衡策略
            if (maxTotalWeight < 0.001d) {
                server =  super.choose(getLoadBalancer(), key);
                if(server == null) {
                    return server;
                }
            } else {
                // generate a random weight between 0 (inclusive) to maxTotalWeight (exclusive)
                //如果大于等于0.001,则生成1个0到最大权重之前的数字
                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.isAlive()) {
                return (server);
            }

        }
        return server;
    }

    class DynamicServerWeightTask extends TimerTask {
        public void run() {
           //启动一个30秒执行的定时任务,用来为每个服务实力计算全重
        }
    }
}

3 RandomRule策略规则

RandomRule策略实现从服务器实例清单中随机选择一个服务实例,策略的实现原理是通过Random对象和服务实例数量,生成一个随机数,通过随机数来确定取那个服务实例,

程序中Thread.yield() 方法,使当前线程由执行状态变成为就绪状态,会把自己CPU执行的时间让掉, 并让自己或者其它的线程运行

// 方便阅读省略部分代码
public class RandomRule extends AbstractLoadBalancerRule {

    Random rand;
    public Server choose(ILoadBalancer lb, Object key) {
        // 。。。
        Server server = null;//重置选取server为空
        while (server == null) {
            //。。。
            List<Server> allList = lb.getAllServers();
            int serverCount = allList.size();
            //。。。
            根据注册的所有服务数量生成随即数并获取指定的服务
            int index = rand.nextInt(serverCount);
            server = upList.get(index);
            //服务部已不存在,则让出CPU时间
            if (server == null) {
                Thread.yield();//让出cpu时间
                continue;
            }
            //服务实力在线状态,则使用此服务
            if (server.isAlive()) {
                return (server);
            }
            // Shouldn't actually happen.. but must be transient or a bug.
            // 没想到Spring也有这样的注释- -!
            server = null;
            Thread.yield();
        }
        return server;
    }
    //。。。
}

4 RetryRule策略规则

RetryRule策略实现一个具备重试机制的实例选择功能,实现默认使用策略还是RoundRobinRule,只是增加了尝试机制 ,在设置的阈值时间范围内,如果没有获取到服务实例就继续尝试获取服务实例

// 方便阅读省略部分代码
public class RetryRule extends AbstractLoadBalancerRule {
	IRule subRule = new RoundRobinRule();
	long maxRetryMillis = 500;//设置小于0的时,默认值

	public void setMaxRetryMillis(long maxRetryMillis) {
		//设置maxRetryMillis 阈值时间
	}

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

		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)) {
					Thread.yield();
				} else {
					break;
				}
			}

			task.cancel();
		}

		if ((answer == null) || (!answer.isAlive())) {
			return null;
		} else {
			return answer;
		}
	}
    //。。。。
}

5 BestAvailableRule 策略规则

 ClientConfigEnableRundRobinRule策略本身并没有实现什么特殊的处理逻辑,内部是调用RoundRobinRule线性轮询机制处理, 此类的目的应该是相让用户通过继承复写此choose方法实现更高级的策略, 从UML图中可以看出BestAvailableRule就是通过复写choose方法实现自身策略

public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {

    RoundRobinRule roundRobinRule = new RoundRobinRule();

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        roundRobinRule = new RoundRobinRule();
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
    	super.setLoadBalancer(lb);
    	roundRobinRule.setLoadBalancer(lb);
    }
    
    @Override
    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");
        }
    }

}

BestAvailableRule 继承ClientConfigEnableRundRobinRule类,依赖负载均衡器的统计对象LoadBalancersStats, BestAvailableRule策略是利用负载均衡器的统计信息选出最空闲的实例

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {

    private LoadBalancerStats loadBalancerStats;
    
    @Override
    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;
        }
    }

6 AvailabilityFilteringRule策略规则

抽象类PredicateBasedRule定义了一个抽象的getPredicate()方法得到一个AbstractServerPredicate对象的实现。在choose方法中,通过AbstractServerPredicate 的实例对象过滤一部分服务然后线性轮训选出服务实例。

AvailabilityFilteringRule策略规则继承了PredicateBasedRule。getPredicate()返回AvailabilityPredicate 对象,策略轮询获取服务,如果满足AvailabilityPredicate 规则就返回服务

    @Override
    public Server choose(Object key) {
        int count = 0;
        //先轮询获取服务
        Server server = roundRobinRule.choose(key);
        //循环10次尝试找到满足条件的服务
        while (count++ <= 10) {
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        //因为只尝试10次,如果集群很大的情况也可能找不到合适的,
        //此时使用父类策略当作备选,即满足条件的服务器按照线性轮训的方式来负载均衡
        return super.choose(key);
    }


AvailabilityPredicate 规则为

  • 是否故障,断路器已经生效断开, 因此服务不能使用
  • 实例的并发请求数量activeRequestCount大于等于请求连接的阈值, 这种情况表示请求数量高负载高,因此服务不能使用
 @Override
    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = getLBStats();
        if (stats == null) {
            return true;
        }
        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
    }
    
    
    private boolean shouldSkipServer(ServerStats stats) {        
        if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped()) 
                || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
            return true;
        }
        return false;
    }

7 ZoneAvoidanceRule策略规则

ZoneAvoidanceRule 策略规则是一种根据区域和可用性筛选服务器的组合规则

//为方便阅读过滤部分代码
public class ZoneAvoidanceRule extends PredicateBasedRule {

    private static final Random random = new Random();
    
    private CompositePredicate compositePredicate;
    
    public ZoneAvoidanceRule() {
        super();
        //Zone区域筛选条件必须可用
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        //服务可用性筛选条件
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        //compositePredicate 等于两个条件组合
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
   

    @Override
    public AbstractServerPredicate getPredicate() {
        return compositePredicate;
    }    
}

   

上一篇:Spring Cloud Ribbon 源代码学习笔记


猜你喜欢

转载自blog.csdn.net/Beijing_L/article/details/121127891