文章目录
- 前文
- Ribbon 负载均衡原理
- Ribbon 源码分析
- RoundRobinRule
- getReachableServers()
- getAllServers()
- incrementAndGetModulo
- 自定义轮询算法
Ribbon 负载均衡原理
负载均衡算法:REST 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务启动后 REST 接口计数从 1 开始
List<ServiceInstance> instances = discoveryClient.getInstances("DEMO-PROVIDER-PAYMENT");
如:
List[0] instances = 127.0.0.1:8001
List[1] instances = 127.0.0.1:8002
1 % 2 = 1 ——》index = 1 list.get(index)
2 % 2 = 0 ——》index = 0 list.get(index)
3 % 2 = 1 ——》index = 1 list.get(index)
4 % 2 = 0 ——》index = 0 list.get(index)
5 % 2 = 1 ——》index = 1 list.get(index)
8001 + 8002 组合成为集群,共计两台机器,集群总数为 2,按照轮询算法原理:
当总请求数为 1 时:1 % 2 = 1 对应下标位置为 1,则获得服务地址为 127.0.0.1:8002
当总请求数为 1 时:2 % 2 = 0 对应下标位置为 0,则获得服务地址为 127.0.0.1:8001
当总请求数为 1 时:3 % 2 = 1 对应下标位置为 1,则获得服务地址为 127.0.0.1:8002
当总请求数为 1 时:4 % 2 = 0 对应下标位置为 0,则获得服务地址为 127.0.0.1:8001
当总请求数为 1 时:5 % 2 = 1 对应下标位置为 1,则获得服务地址为 127.0.0.1:8002
以此类推…
Ribbon 源码分析
先来看下 IRule 的 UML 图
下面是 IRule 的接口源码
IRule 的实现类
RoundRobinRule
来看下默认的轮询算法源码 RoundRobinRule.java
可以发现 RoundRobinRule 类中定义了一个 AtomicInteger, 并初始化为 0
再来看下 RoundRobinRule 实现 IRule 的 choose 方法
public Server choose(ILoadBalancer lb, Object key) {
// 如果 lb 等于 null,也就是说没有负载均衡
if (lb == null) {
log.warn("no load balancer");
// 返回 null
return null;
}
Server server = null;
int count = 0;
// 如果服务器为 null 并且 count++ < 10
while (server == null && count++ < 10) {
// 获取可达(健康的)的机器(服务器)
List<Server> reachableServers = lb.getReachableServers();
// 获取所有机器
List<Server> allServers = lb.getAllServers();
// 获取健康服务的数量
int upCount = reachableServers.size();
// 获取所有服务的数量,这里也就是上面所说的获取集群总数量如 8001 + 8002 两台机器
int serverCount = allServers.size();
// 一般只要有服务启动成功,通常不会执行这个 if
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
// 这里得到的值为 1,为什么是 1,下面会讲到
int nextServerIndex = incrementAndGetModulo(serverCount);
// 获取下一个服务下标,如 0、1、0、1
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;
}
getReachableServers()
返回可访问的服务器
getAllServers()
返回所有已知的服务器,包括可访问的和不可访问的
incrementAndGetModulo
// 根据上面的代码如果集群数量为 2,那么这里 modulo 传的就是 2
private int incrementAndGetModulo(int modulo) {
// 以下是一个自旋锁
for (;;) {
// nextServerCyclicCounter 上面已经介绍过,初始值为 0,那么也就是说 current = 0
int current = nextServerCyclicCounter.get();
// (0 + 1)% 2 = 1,根据上面提到的负载均衡原理,这里的下标就是 1
int next = (current + 1) % modulo;
// 这里使用的是 CAS (比较并交换),将当前的值(current)传进去,下一次的值传进去
// 假设 next 值没修改过的话,那么 next 值就是 1,否则开始自旋,不断比较
if (nextServerCyclicCounter.compareAndSet(current, next))
// 返回 next 值
return next;
}
}
自定义轮询算法
先定义一个接口
package com.java.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
/**
* @author Woo_home
* @create 2020/3/27 13:13
*/
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
创建实现类
package com.java.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Woo_home
* @create 2020/3/27 13:14
*/
@Component // 一定要加上这个注解,否则无法扫描
public class MyLB implements LoadBalancer{
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
// 这一串数字是 Integer.MAX_VALUE 的值
next = current >= 2147483647 ? 0 : current + 1;
} while (!this.atomicInteger.compareAndSet(current,next)); // 这里不要忘记加上 !,不然打印的结果出乎你的意料
System.out.println("****** 第 " + next + " 次访问 ****** : ");
// 返回的是第几次访问
return next;
}
// 负载均衡算法:REST 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务启动后 REST 接口计数从 1 开始
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
修改消费者端 Controller
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB() {
// 获取服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("DEMO-PROVIDER-PAYMENT");
if (instances == null || instances.size() <= 0) {
return null;
}
// 使用自定义的轮询算法
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb",String.class);
}
访问页面
控制台输出