【云原生】springcloud09——但愿发长久,空手撕Ribbon

前 言 🍉 作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端 ☕专栏简介:深入、全面、系统的介绍springcloud与springcloud Alibaba微服务常用技术栈 🌰 文章简介:本文将介绍Ribbon负载均衡的原理,深入源码进行分析,并且手撕轮询算法,建议收藏备用,创作不易,敬请三连哦 🥒文章推荐: 微服务架构与springcloud 01——微服务入门 微服务架构与springcloud02——父工程构建及支付模块实现 微服务架构与springcloud03——项目热部署与消费者订单模块 微服务架构与springcloud04——Eureka服务注册与发现 springcloud05——Zookeeper实现支付微服务 【云原生】springcloud06——订单服务注册zookeeper 【云原生】springcloud07—Consul的服务注册与发现 【云原生】springcloud08——Ribbon负载均衡调用

文章目录

  • 1.Ribbon默认轮询算法原理
  • 2.RoundRobinRule源码解读
  • 3.手写轮询算法
    • 3.1 8001和8002微服务改造
    • 3.2 订单微服务改造
    • 3.3 测试

1.Ribbon默认轮询算法原理

先将注解@RibbonClient注释掉。让它恢复到最开始的轮询算法。

轮询算法的原理如下。妙不妙?

2.RoundRobinRule源码解读

我们先解读下RoundRobinRule轮询算法的源码实现,方便后面仿照轮询算法实现默认的负载均衡算法。

先看接口IRule

代码语言:javascript
复制
public interface IRule {
    Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);

ILoadBalancer getLoadBalancer();

}

里面有一个choose方法,看看在RoundRobinRule中的具体实现吧。

代码语言:javascript
复制
  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;
        }
    }
}</code></pre></div></div><p>看看incrementAndGetModulo方法</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">  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;
}</code></pre></div></div><h2 id="65200" name="3.%E6%89%8B%E5%86%99%E8%BD%AE%E8%AF%A2%E7%AE%97%E6%B3%95">3.手写轮询算法</h2><h3 id="65201" name="3.1-8001%E5%92%8C8002%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%94%B9%E9%80%A0">3.1 8001和8002微服务改造</h3><p>在8001和8002的PaymentController中加上这个方法,用于测试我们的自定义轮询:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">@GetMapping(&#34;/lb&#34;)

public String getPaymentLB(){
return serverPort;
}

3.2 订单微服务改造

将订单微服务的负载均衡注解去掉

在springcloud包下新建lb.ILoadBalancer接口(自定义负载均衡机制(面向接口))

代码语言:javascript
复制
public interface LoadBalancer {

// 传入具体的服务集合,返回服务实例
ServiceInstance instances(List&lt;ServiceInstance&gt; instances);

}

在lb包下新建自定义ILoadBalancer接口的实现类,实现负载均衡的核心逻辑。下面用到了CAS自旋锁的知识,让代码很健壮。

代码语言:javascript
复制
@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 &gt;= 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(&#34;****访问次数:&#34; + next);
    // 返回的next即visitCount自增成功后的值
    return next;
}

@Override
public ServiceInstance instances(List&lt;ServiceInstance&gt; instances) {
    // 轮询算法
    int index = getAndIncrement() % instances.size();
    return instances.get(index);
}

}

接着在我们的OrderController代码逻辑里来引入自己的自旋锁吧。

代码语言:javascript
复制
    @Resource
private ILoadBalancer iLoadBalancer;
@Resource
private DiscoveryClient discoveryClient;

@GetMapping(&#34;/payment/lb&#34;)
public String getPaymentLB(){
    List&lt;ServiceInstance&gt; instances = discoveryClient.getInstances(&#34;CLOUD-PAYMENT-SERVICE&#34;);

    //判断服务有效
    if (instances ==null || instances.size() &lt;=0){
        return null;
    }
    ServiceInstance serviceInstance = loadBalancer.instances(instances);

    URI uri = serviceInstance.getUri();
    System.out.println(uri);

    return restTemplate.getForObject(uri+&#34;/payment/lb&#34;,String.class);

}</code></pre></div></div><h3 id="65213" name="3.3-%E6%B5%8B%E8%AF%95">3.3 测试</h3><p>启动Eureka Server集群7001,7002,支付微服务集群8001,8002,订单80微服务。</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:88.65%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723319134236298374.png" /></div></div></div></figure>