1. Ribbon 负债均衡策略
命名 | 描述 | |
---|---|---|
RandomRule | 随机策略 | 随机选择server |
RoundRobinRule | 轮询策略 | 按照顺序选择server(ribbon默认策略) |
RetryRule | 重试策略 | 在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server |
BestAvailableRule | 最低并发策略 | 逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) |
ResponseTimeWeightedRule | 响应时间加权重策略 | 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule | 区域权重策略 | 综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server |
2. 源码分析
负载均衡的策略的核心实现接口是IRule 接口中的choose()方法,不同的负载策略通过实现choose()方法来实现不同逻辑
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();
}
2.1. 随机策略
随机策略的实现类是RandomRule类,实现choose()方法
public Server choose(ILoadBalancer lb, Object key) {
// 传入的LoadBalancer是空,直接返回null
if (lb == null) {
return null;
}
Server server = null;
// 如果没有获取到随机服务,一直循环直到获取到服务
while (server == null) {
// 线程如果被中断,返回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;
}
// 关键方法,生成随机一个0--serverCount 之间的随机数作为下标
int index = chooseRandomInt(serverCount);
// 根据下表从可用服务列表中拿Server实例
server = upList.get(index);
// server 为空,这种情况是因为serverList在这个时候正在被修正
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.
*/
// 通知线程调度器,让出当前CPU
// 这里的yield,是和上面第10行代码配合使用的,这是一种防御性编程的体现
Thread.yield();
continue;
}
// 如果server是可用的直接返回
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
生成随机数,使用ThreadLocalRandom 然后在当前的thread中通过nextInt传入了一个serverCount,然后就会返回0到serverCount中的任意一个值。
// 生成随机数
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
2.2. 轮询策略
轮询策略实现类RoundRobinRule
public Server choose(ILoadBalancer lb, Object key) {
// 如果没有LoadBalancer直接返回null
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
// 循环获取可用服务,直到获取服务和获取次数在10次以下结束循环
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);
// 服务为null时,让出CPU
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
// 校验服务可用时直接返回
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
// 利用计数器限制循环次数,如果10次还没有获取到可用服务,打印一行日志
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
生成轮询服务下标
private int incrementAndGetModulo(int modulo) {
// 自选的方式获取轮询下标
for (;;) {
// 原子操作,上一次被调用的服务下标
int current = nextServerCyclicCounter.get();
// 上一次访问机器的下标+1和服务器数量相除取余,得到当前的服务下标
int next = (current + 1) % modulo;
// 通过CAS对比替换上一次调用服务的值为next值
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}