Ribbon负载均衡策略(1)
AbstractLoadBalancerRule
抽象类里定义了负载均衡器ILoadBalancer,该对象能够在具体实现选择服务策略时,获取到一些负载均衡中维护的信息,来作为分配依据,并以此设计一些算法实现针对特定场景的高效策略。
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
private ILoadBalancer lb;
@Override
public void setLoadBalancer(ILoadBalancer lb){
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer(){
return lb;
}
}
负载均衡策略
RandomRule 随机选择
实现IRule接口choose()方法,通过调用AbstractLoadBalancerRule.getLoadBalancer()获取负载均衡器,委托给自身choose()方法来处理。通过rand.nextInt()方法,从有用的服务实例中随机选择。(Randomly choose from all living servers)
public class RandomRule extends AbstractLoadBalancerRule {
Random rand;
@Override
public Server choose(Object key) {
//实现IRule接口choose()方法,通过调用AbstractLoadBalancerRule.getLoadBalancer()获取负载均衡器,委托给自身choose()方法来处理
return choose(getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
//todo lb非空判断
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
//todo 无注册实例 返回null
//从健康实例中 随机选择
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
}
RoundRobinRule 线性轮询
与RandomRule差不多,不过只尝试10次去获取有用的服务实例;调用的是incrementAndGetModulo()线性轮询实例。
public Server choose(ILoadBalancer lb, Object key) {
Server server = null;
int count = 0;
//尝试10次放弃
while (server == null && count++ < 10) {
//线性轮询
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
RetryRule 重试机制
该策略实现了一个具备重试机制的实例选择功能。
可以选择使用哪个基础策略,默认使用RoundRobinRule(线性轮询),在结束时间内进行反复尝试的策略。
结束时间 :long deadline = requestTime + maxRetryMillis;
/*
* Loop if necessary. Note that the time CAN be exceeded depending on the
* subRule, because we're not spawning additional threads and returning
* early.
*/
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)) {
/* pause and retry hoping it's transient */
Thread.yield();
} else {
break;
}
}
task.cancel();
}
if ((answer == null) || (!answer.isAlive())) {
return null;
} else {
return answer;
}
}
WeightedResponseTimeRule 权重
该策略是继承RoundRobinRule,对原有的进行扩展。它的实现有3个核心内容:定时任务、权重计算、实例选择。响应时间越短,权重越高。
定时任务
在WeightedResponseTimeRule初始化(initialize)的时候,会通过设置一个定时任务,为每个服务实例计算权重,serverWeightTaskTimerInterval默认30s执行1次。
serverWeightTimer.schedule(new DynamicServerWeightTask(), 0,serverWeightTaskTimerInterval);
class DynamicServerWeightTask extends TimerTask {
public void run() {
ServerWeight serverWeight = new ServerWeight();
try {
//计算权重
serverWeight.maintainWeights();
} catch (Exception e) {
logger.error("Error running DynamicServerWeightTask for {}", name, e);
}
}
}
权重计算 serverWeight.maintainWeights()
WeightedResponseTimeRule里有个volatile List<Double> accumulatedWeights的成员变量,是用于存储权重,与负载均衡器的服务实例清单的位置一一对应。
public void maintainWeights() {
ILoadBalancer lb = getLoadBalancer();
//todo ...
try {
logger.info("Weight adjusting job started");
AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
LoadBalancerStats stats = nlb.getLoadBalancerStats();
// no statistics, nothing to do return ...
//计算所有实例的平均响应时间的总和totalResponseTime
double totalResponseTime = 0;
// find maximal 95% response time
for (Server server : nlb.getAllServers()) {
// this will automatically load the stats if not in cache
ServerStats ss = stats.getSingleServerStat(server);
totalResponseTime += ss.getResponseTimeAvg();
}
// so that the longer the response time, the less the weight and the less likely to be chosen
Double weightSoFar = 0.0;
// create new list and hot swap the reference
List<Double> finalWeights = new ArrayList<Double>();
for (Server server : nlb.getAllServers()) {
ServerStats ss = stats.getSingleServerStat(server);
//weight for each server is (sum of responseTime of all servers - responseTime)
//实例区间宽度=总的响应时间 - 实例的平均响应时间
double weight = totalResponseTime - ss.getResponseTimeAvg();
//计算权重区间 如A[0,100] B(100,200] C(200,360]
weightSoFar += weight;
finalWeights.add(weightSoFar);
}
//设置权重成员accumulatedWeights
setWeights(finalWeights);
} catch (Exception e) {
logger.error("Error calculating server weights", e);
} finally {
serverWeightAssignmentInProgress.set(false);
}
}
实例选择
获取最后一个实例权重,得知整个标尺大小,满足要求(>=0.001),就从标尺里间随机选择1个数,落在哪服务实例的区间就选择哪个实例。如A[0,100] B(100,200] C(200,360]
public Server choose(ILoadBalancer lb, Object key) {
//todo ...
while (server == null) {
// get hold of the current reference in case it is changed from the other thread
List<Double> currentWeights = accumulatedWeights;
//todo ...
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) {
//如果maxTotalWeight <0.001,则调用直父类RoundRobinRule的选择策略
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);
}
//todo ...
}
return server;
}