《 Ribbon核心组件IRule的使用及自定义负载均衡算法 》
前言
在上一篇文章中,主要完成了 《 Rest微服务加入Ribbon负载均衡客户端组件实现负载均衡 》,本篇将带领读者一步一步认识Ribbon的核心插件 “ IRule ”的常用 API 以及自定义算法规则详细说明,本篇博文涉及的服务模块包括:
- 修改消费者模块 “ microservice-consumer-80 ”,新增具体的算法配置类,实现指定规则的调用;
Ribbon核心组件IRule的使用及自定义负载均衡算法
1、IRule 常见的API
这儿先细列哈Ribbon核心组件IRule中常见的几个算法API,一般情况下,这几个API已经可以满足实际的业务需求了,如下:
API | 说明 |
RoundRobinRule | 广为人知和最基本的负载平衡策略,即轮询算法。(是Ribbon默认的负载均衡机制) |
RandomRule | 一种随机分配现有流量的负载平衡策略,即随机访问算法 |
RetryRule | 先按照 RoundRobinRule 的策略访问服务,如果访问的服务宕机或者出现异常的情况,则在指定时间内会进行重试,访问其它可用的服务 |
BestAvailableRule | 首先会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务访问 |
ZoneAvoidanceRule | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 |
AvailabilityFilteringRule | 首先会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问 |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。 刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够, 会切换到WeightedResponseTimeRule |
接下将列举前三个API的源码,只为展示其表面的算法,当然更多的内部实现细节,请转到Ribbon官网咯。
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 <[email protected]>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule
{
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);
public RoundRobinRule()
{
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb)
{
this();
setLoadBalancer(lb);
}
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;
}
/**
* 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 (;;)
{
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
}
RandomRule(这个博主在实际的业务中也常常遇到:首先获取服务列表,再获取服务总的个数,获取这个总数内的随机数,得到的这个随机数即服务的下标,拿到下标后,再去服务列表中取指定的服务名称即可,不知道你们有没有遇到过呢!):
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* A loadbalacing strategy that randomly distributes traffic amongst existing
* servers.
*
* @author stonse
*
*/
public class RandomRule extends AbstractLoadBalancerRule
{
/**
* Randomly choose from all living servers
*/
@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;
}
protected int chooseRandomInt(int serverCount)
{
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
}
RetryRule:
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
/**
* Given that {@link IRule} can be cascaded, this {@link RetryRule} class allows
* adding a retry logic to an existing Rule.
*
* @author stonse
*
*/
public class RetryRule extends AbstractLoadBalancerRule
{
IRule subRule = new RoundRobinRule();
long maxRetryMillis = 500;
public RetryRule()
{
}
public RetryRule(IRule subRule)
{
this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
}
public RetryRule(IRule subRule, long maxRetryMillis)
{
this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
this.maxRetryMillis = (maxRetryMillis > 0) ? maxRetryMillis : 500;
}
public void setRule(IRule subRule)
{
this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
}
public IRule getRule()
{
return subRule;
}
public void setMaxRetryMillis(long maxRetryMillis)
{
if (maxRetryMillis > 0)
{
this.maxRetryMillis = maxRetryMillis;
} else
{
this.maxRetryMillis = 500;
}
}
public long getMaxRetryMillis()
{
return maxRetryMillis;
}
@Override
public void setLoadBalancer(ILoadBalancer lb)
{
super.setLoadBalancer(lb);
subRule.setLoadBalancer(lb);
}
/*
* 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;
}
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
}
2、实现随机访问策略,一种随机分配现有流量的负载平衡策略
修改消费者服务 “ microservice-consumer-80 ” 的 “ ConfigBean ” 类,新增内容:
@Bean
public IRule randomIRule() {
return new RandomRule();
}
完整内容:
package com.huazai.springcloud.cfgbeans;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
*
* <p>
*
* @ClassName : ConfigBean
* </p>
* <p>
* @Description : TODO
* </p>
*
* @Author : HuaZai
* @ContactInformation : [email protected]
* @Date : 2018年05月23日 下午9:01:21
* @Version : V1.0.0
*
* @param
*/
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
@Bean
public IRule randomIRule() {
return new RandomRule();
}
}
测试:
首先启动 Eureka 集群,再启动三台提供者服务器,最后启动消费者服务器,并访问消费者服务器地址,反复刷新,发现访问的服务器是没有规则的,随机访问,注意数据库的变化,如下图:
3、自定义访问策略
新建一个类 “ IRuleConfig ”,用于定义基于 Ribbon 的访问策略,完整内容如下:
package com.huazai.springcloud.cfgbeans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description 自定义访问策略
* </ul>
* @className IRuleConfig
* @package com.huazai.springcloud.cfgbeans
* @createdTime 2018年05月23日 下午3:57:31
*
* @version V1.0.0
*/
@Configuration
public class IRuleConfig
{
@Bean
public IRule myIRule()
{
// return new RetryRule();
return new RandomRule();
// return new BestAvailableRule();
// return new ZoneAvoidanceRule();
// return new AvailabilityFilteringRule();
// return new WeightedResponseTimeRule();
}
}
修改消费者 “ microservice-consumer-80 ” 的主启动类,新增注解 “ @RibbonClient ” ,其目的是在启动该微服务的时候就能去加载自定义 Ribbon 配置类,从而使配置立即生效,完整内容如下:
package com.huazai.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import com.huazai.springcloud.cfgbeans.ConfigBean;
import com.huazai.springcloud.cfgbeans.IRuleConfig;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description
* <li>服务消费者
* </ul>
* @className MicroServiceConsumerApp
* @package com.huazai.springcloud
* @createdTime 2018年05月22日 下午3:47:02
*
* @version V1.0.0
*/
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "MICROSERVICE-PROVIDER", configuration = IRuleConfig.class)
public class MicroServiceConsumerApp
{
public static void main(String[] args)
{
SpringApplication.run(MicroServiceConsumerApp.class, args);
}
}
测试
首先启动 Eureka 集群,再启动三台提供者服务器,最后启动消费者服务器,并访问消费者服务器地址,反复刷新,发现访问的服务器是没有规则的,随机访问,注意数据库的变化,和上面一样的哈,如下图:
4、新增需求
需求一:由于公司资源有限(说白了就是降低成本),现在有三台服务器,配置由高到低排序 1号服务器 > 2号服务器 > 3号服务器,要求:1号服务器承载50%的流量,2号服务器承载30%的流量,3号服务器承载20%的流量,要求自定义Ribbon 算法实现,这个是当年博主遇到的一个很犯贱的问题,对于当时而言很难,因为什么都要自己写,不像现在什么都封装成了API,直接调用即可,当然现在一般的互联网公司很难遇到了,所以这儿的实现就省略咯。。。
需求二:根据客户那边对环境的需求,指定方案需要对流量进行绝对的限制,但依然是轮询机制,例如:1号服务器被调用3次,之后,2号服务器被调用3次,之后,3号服务器被调用3次,因为也基于轮询机制的,所以可以直接在上面的 " RoundRobinRule.java " 的源代码中进行简单修改即可。
1)新增自定义算法 “ Custom_RandomRule ” 类,并重写IRule的 “ choose ” 方法和IClientConfigAware的 “ initWithNiwsConfig”方法,完整内容如下:
package com.huazai.springcloud.cfgbeans;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description
* <li>自定义轮询机制
* </ul>
* @className Custom_RandomRule
* @package com.huazai.springcloud.cfgbeans
* @createdTime 2018年05月24日 下午4:49:51
*
* @version V1.0.0
*/
@SuppressWarnings("unused")
public class Custom_RandomRule extends AbstractLoadBalancerRule
{
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private int total = 0; // 总的被调用次数
private int currentIndex = 0;// 当前提供服务的机器号
private static Logger log = LoggerFactory.getLogger(Custom_RandomRule.class);
public Server choose(ILoadBalancer lb, Object key)
{
if (lb == null)
{
log.warn("no load balancer");
return null;
}
Server server = null;
while (server == null)
{
if (Thread.interrupted())
{
return null;
}
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;
}
// 实现每台服务请求三次的业务逻辑
if (total < 3)
{
server = reachableServers.get(currentIndex);
total++;
} else
{
total = 0;
currentIndex++;
if (currentIndex >= upCount)
{
currentIndex = 0;
}
}
if (server == null)
{
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe()))
{
return (server);
}
// Next.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
// TODO Auto-generated method stub
}
}
修改配置文件 “ IRuleConfig ” ,将 IRule 修改为自定义的服务调用算法机制,完整内容如下:
package com.huazai.springcloud.cfgbeans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description 自定义访问策略
* </ul>
* @className IRuleConfig
* @package com.huazai.springcloud.cfgbeans
* @createdTime 2018年05月23日 下午3:57:31
*
* @version V1.0.0
*/
@Configuration
public class IRuleConfig
{
@Bean
public IRule myIRule()
{
return new Custom_RandomRule(); // 自定义算法
}
}
效果图如下:
GitLab 源码地址:
项目源码地址(zip格式的工程包):
好了,关于 Spring Cloud 进阶--Ribbon核心组件IRule的使用及自定义负载均衡算法 就写到这儿了,如果还有什么疑问或遇到什么问题欢迎扫码提问,也可以给我留言哦,我会一一详细的解答的。
歇后语:“ 共同学习,共同进步 ”,也希望大家多多关注CSND的IT社区。
作 者: | 华 仔 |
联系作者: | [email protected] |
来 源: | CSDN (Chinese Software Developer Network) |
原 文: | https://blog.csdn.net/Hello_World_QWP/article/details/88185574 |
版权声明: | 本文为博主原创文章,请在转载时务必注明博文出处! |