前 言
🍉 作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端
☕专栏简介:深入、全面、系统的介绍springcloud与springcloud Alibaba微服务常用技术栈
🌰 文章简介:本文将介绍Ribbon负载均衡的原理,深入源码进行分析,并且手撕轮询算法,建议收藏备用,创作不易,敬请三连哦
🥒文章推荐:
微服务架构与springcloud 01——微服务入门
微服务架构与springcloud02——父工程构建及支付模块实现
微服务架构与springcloud03——项目热部署与消费者订单模块
微服务架构与springcloud04——Eureka服务注册与发现
springcloud05——Zookeeper实现支付微服务
【云原生】springcloud06——订单服务注册zookeeper
【云原生】springcloud07—Consul的服务注册与发现
【云原生】springcloud08——Ribbon负载均衡调用
1.Ribbon默认轮询算法原理
先将注解@RibbonClient
注释掉。让它恢复到最开始的轮询算法。
轮询算法的原理如下。妙不妙?
2.RoundRobinRule源码解读
我们先解读下RoundRobinRule
轮询算法的源码实现,方便后面仿照轮询算法实现默认的负载均衡算法。
先看接口IRule
。
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
里面有一个choose
方法,看看在RoundRobinRule
中的具体实现吧。
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
// 如果没有负载均衡算法,返回null
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
//获取状态为up(活着的)服务器
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
看看incrementAndGetModulo方法
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
3.手写轮询算法
3.1 8001和8002微服务改造
在8001和8002的PaymentController中加上这个方法,用于测试我们的自定义轮询:
@GetMapping("/lb")
public String getPaymentLB(){
return serverPort;
}
3.2 订单微服务改造
将订单微服务的负载均衡注解去掉
在springcloud包下新建lb.ILoadBalancer接口(自定义负载均衡机制(面向接口))
public interface LoadBalancer {
// 传入具体的服务集合,返回服务实例
ServiceInstance instances(List<ServiceInstance> instances);
}
在lb包下新建自定义ILoadBalancer接口的实现类,实现负载均衡的核心逻辑。下面用到了CAS自旋锁的知识,让代码很健壮。
@Component
public class MyLB implements LoadBalancer {
// 新建一个原子整形实例,记录访问次数,使线程安全
private AtomicInteger visitCount = new AtomicInteger(0);
public final int getAndIncrement() {
int current;
int next;
do {
current = visitCount.get();
//如果current是最大值,重新计算,否则加1(防止越界),
// 正常情况肯定不会出现越界的情况,但是我们可以学习源码这种方式,提升代码健壮性
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
// 当visitCount与current相等时,说明cas成功将visitCount更新为next,终止循环
// 当visitCount与current不相等时,说明有其他线程操作atomicInteger,返回true,取反为false,循环操作
} while (!this.visitCount.compareAndSet(current, next));
System.out.println("****访问次数:" + next);
// 返回的next即visitCount自增成功后的值
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> instances) {
// 轮询算法
int index = getAndIncrement() % instances.size();
return instances.get(index);
}
}
接着在我们的OrderController代码逻辑里来引入自己的自旋锁吧。
@Resource
private ILoadBalancer iLoadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/payment/lb")
public String getPaymentLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
//判断服务有效
if (instances ==null || instances.size() <=0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
System.out.println(uri);
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
3.3 测试
启动Eureka Server集群7001,7002,支付微服务集群8001,8002,订单80微服务。