1.介绍
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
Ribbon是一个为客户端提供负载均衡功能的服务,它内部提供了一个叫做ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、获取所有的服务器列表、获取可用的服务器列表等等。
2.Ribbon默认自带的负载规则
- RoundRobinRule 轮询
- RandomRule 随机
- RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重
- WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
3.Ribbon的负载均衡和Rest调用
Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例。
Ribbon在工作时分成两步:
1.先选择EurekaServer ,它优先选择在同一个区域内负载较少的server。
2.再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
4.客户端负载均衡VS服务器端负载均衡
1_客户端端负载均衡
ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这就是客户端负载均衡;即在客户端就进行负载均衡算法分配。
2_服务端负载均衡
例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。需要在nginx配置所有的服务提供者信息。
5.轮训规则的替换
⚠️这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
也就是说不要将Ribbon配置类与主启动类同包)
自定义负载轮训,当然方法不止一种,可以看链接。
6.源码
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) {
}
}