轮询算法
默认负载轮训算法 : rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标(每次服务重启动后rest接口计数从1开始)
List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);
当:
List[0] instances = 127.0.0.1:8002
List[1] instances = 127.0.0.1:8001
则:
8001+ 8002组合成为集群,它们共计2台机器,集群总数为2
且:
- 当总请求数为1时:1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
- 当总请求数位2时:2%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
- 当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
- 当总请求数位4时:4%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
- 如此类推…
源码分析
IRule接口
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();
}
RoundRobinRule实现类
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*
* @author stonse
* @author Nikos Michalakis <nikos@netflix.com>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {
// 定义了原子类的Integer,是线程安全的Integer
private AtomicInteger nextServerCyclicCounter;
// 定义了 服务存活的变量为真
private static final boolean AVAILABLE_ONLY_SERVERS = true;
// 定义了 所以服务的变量为假
private static final boolean ALL_SERVERS = false;
// 定义了 日志的对象
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
// 设置 默认的线程安全Integer为0
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
// 给lb设置负载均衡
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
//重点关注这方法。
public Server choose(ILoadBalancer lb, Object key) {
// 如果负载均衡未开启为null 就提示没有负载均衡,并返回null
if (lb == null) {
log.warn("no load balancer");
return null;
}
// 设定服务初始值为null
Server server = null;
// 计数数目初始为0
int count = 0;
// 注意下边是一个循环,我们循环获取需要提供服务的节点,最多失败10次
while (server == null && count++ < 10) {
// 获取目前存活的服务主机(是真可用的服务节点,因为erueka有AP自我保护)
List<Server> reachableServers = lb.getReachableServers();
// 获取目前所有的服务主机
List<Server> allServers = lb.getAllServers();
// 设置当前存活的服务节点数量
int upCount = reachableServers.size();
// 设置所有的服务节点数量
int serverCount = allServers.size();
// 如果当前存活的主机和所有的主机都是0就提示没有可用的主机,并返回null
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
// 我们调用了incrementAndGetModulo方法并传入了所有的服务节点数量
int nextServerIndex = incrementAndGetModulo(serverCount);
// 获取本次需要提供服务节点的索引
server = allServers.get(nextServerIndex);
// 如果本次需要提服务的节点如果为null,线程会进入让步状态
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
// 如果需要提供服务的节点是可用状态,就使其进入准备状态并返回该服务节点
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// 设置服务节点变量为null
// Next.
server = null;
}
// 上边的循环已经进行了10次,还没有得到需要提供服务的节点,打印信息,返回null的服务节点对象了。
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
// 这个方法并不是死循环,而是一个自旋锁
for (;;) {
// 获取之前已经请求的次数 (我觉得应该是上次请求提供的服务节点索引更为合适,使用‘上次索引+1’和使用‘原始请求的次数+1’都不影响的最终结果,更推荐记录上次索引的,内存占用小)
int current = nextServerCyclicCounter.get();
// 将之前的请求次数+1,也就是这次请求的次数了。使用本次的次数对服务节点的数量进行取模运算,得到这次请求需要的节点索引next
int next = (current + 1) % modulo;
// 使用CAS 比较并交换.(如果我传入current和内存中的一样,我就用next的值与其进行交换,否则将继续这个自旋锁)
if (nextServerCyclicCounter.compareAndSet(current, next))
// 返回本次需要提供服务的服务节点索引
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}