Ribbon与客户端Load balance

Qualifications Before Read

  • Experience in RxJava/Promise
  • Experience in Feign/Retrofit' inteceptor
  • Familiarity with debug tools

SpringCloud层分析

我们基于拦截器机制并直接进入到了如下位置

org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient

最终实现类是

com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer

如下,非常类似于NodeJS/Netty中的await Promise,也就是基于事件队列的实现机制,这类代码的特点就是写起来非常爽,读起来需要一定阅读量(如果阅读RxJava困难的话,可以学习前端的Promise加速理解)。

为了节约版面,我将所有的异常处理全部去掉了,代码核心在submit中,下面的代码只是一个柯里化

LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
// 提交后返回的是一个Promise,并没有立刻获取到
return command.submit(
    // 这里的入参是一个Operation接口,初学者可能看不懂代码是怎么来回跳转的
    // 实际上它本质是柯里化(Currying),用Interface模拟函数
    new ServerOperation<T>() {
        @Override
        public Observable<T> call(Server server) {
            // 此处的Server已经是**负载均衡后**的了,后面请求为原生HTTP请求
            URI finalUri = reconstructURIWithServer(server, request.getUri());
            S requestForServer = (S) request.replaceUri(finalUri);
            return Observable
                .just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
        }
    })
    // 类似于前端的await,在Java中用CountDownLatch实现
    .toBlocking()
    .single();

整理后,获取单个Server的关键代码如下,主要在submit中这一行

// 获取当前的服务列表xml,并缓存到本地
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);

本人不会全屏贴代码供读者参考的,因此跳转分析技巧需要自己多练习


ILoadBalancer分析

在Ribbon中,请求关键伪代码如下

a-->b: getLoadBalancer
b-->s: chooseServer
s-->Server: IRule

整个流程与ElasticSearch等客户端框架相差不大,比如最常见的com.netflix.loadbalancer.BaseLoadBalancer

默认负载均衡算法(RoundRobinRule)与Elastic完全一样,也是通过数组取余进行计算

  • RoundRobinRule: 通过数组环与Atomic自增取余