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);
}
}