2020教你最新的Spring Cloud Ribbon 源码解析


代码准备

依赖关系

pom 依赖

加入nacos 服务发现即可,内部引用了spring-cloud-ribbon相关依赖

<dependency>

  <groupId>com.alibaba.cloud</groupId>

  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

</dependency>

调用客户端

我们这里以最简单的 RestTemplate 调用开始使用Ribbon

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

// Controller 使用restTemplate 调用服务提供方接口

ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/req", String.class);


源码解析

创建调用拦截器

1. 获取全部 @LoadBalanced标记的RestTemplate

public class LoadBalancerAutoConfiguration {

@LoadBalanced

@Autowired(required = false)

private List<RestTemplate> restTemplates = Collections.emptyList();

}


2. 增加 LoadBalancerInterceptor 处理逻辑

没有引入 spring-retry使用的是

@BeanpublicLoadBalancerInterceptorribbonInterceptor(){

return new LoadBalancerInterceptor();

}

引入 spring-retry 使用的是

@Bean

@ConditionalOnMissingBean

public RetryLoadBalancerInterceptor ribbonInterceptor() {

return new RetryLoadBalancerInterceptor();

}

LoadBalancerInterceptor 业务逻辑

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    @Override

    public ClientHttpResponse intercept() {

        final URI originalUri = request.getURI();

        // http://demo-provider/req 截取 demo-provider 服务名称

        String serviceName = originalUri.getHost();

        // 默认注入的 RibbonAutoConfiguration.RibbonLoadBalancerClient

        return this.loadBalancer.execute(serviceName,

                // 创建请求对象

                this.requestFactory.createRequest(request, body, execution));

    }

}

执行拦截器

3. RibbonLoadBalancerClient执行

//RibbonAutoConfiguration默认注入的RibbonLoadBalancerClient

@Bean

@ConditionalOnMissingBean(LoadBalancerClient.class)

public LoadBalancerClient loadBalancerClient() {

  return new RibbonLoadBalancerClient(springClientFactory());

}

4.execute执行

public class RibbonLoadBalancerClient implements LoadBalancerClient {

    public <T> T execute(){

        //获取具体的ILoadBalancer实现

        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

        // 调用ILoadBalancer 实现获取Server

        Server server = getServer(loadBalancer, hint);

        RibbonServer ribbonServer = new RibbonServer(serviceId, server,

                isSecure(server, serviceId),

                serverIntrospector(serviceId).getMetadata(server));

        //获取状态记录器,保存此次选取的server

        RibbonLoadBalancerContext context = this.clientFactory

                .getLoadBalancerContext(serviceId);

        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        T returnVal = request.apply(serviceInstance);

        statsRecorder.recordStats(returnVal);

        return returnVal;

    }

}

获取ILoadBalancer

5 SpringClientFactory

// bean 工厂生成LoadBalancer 的实现

protected ILoadBalancer getLoadBalancer(String serviceId) {

return this.springClientFactory.getLoadBalancer(serviceId);

}

// 具体生成逻辑看 RibbonClientConfiguration,这个Bean 只有工厂调用的时候才会创建

@Bean

@ConditionalOnMissingBean

public ILoadBalancer ribbonLoadBalancer(IClientConfig config,

ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,

IRule rule, IPing ping, ServerListUpdater serverListUpdater) {

return new ZoneAwareLoadBalancer<>();

}

6.创建LoadBalancer 的依赖要素

以上默认实现参考 RibbonClientConfiguration. ZoneAwareLoadBalancer

获取服务实例

//Server server = getServer(loadBalancer, hint); 4. excute 方法

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {

return loadBalancer.chooseServer(hint != null ? hint : "default");

}

7. ZoneAwareLoadBalancer

public class ZoneAwareLoadBalancer{

    public ZoneAwareLoadBalancer() {

        // 调用父类初始化方法。 这里会开启实例维护的定时任务等 (具体解析参考 扩展部分)

        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);

    }

    @Override

    public Server chooseServer(Object key) {

        // 若是使用的 Nacos 服务发现,则没有 Zone 的概念,直接调用父类的实现

        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {

            return super.chooseServer(key);

        }

        // 以下为有 Zone 的概念 例如 Eureka  (具体)

        ...

        return server;

    }

}

父类调用IRule实现选择Server

public Server chooseServer(Object key) {

return rule.choose(key);

}


8.PredicateBasedRule 选择规则

public abstract class PredicateBasedRule {

    @Override

    public Server choose(Object key) {

        ILoadBalancer lb = getLoadBalancer();

        // 获取断言配置

        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);

        if (server.isPresent()) {

            return server.get();

        } else {

            return null;

        }

    }

}

9. ZoneAvoidancePredicate服务列表断言

public class ZoneAvoidancePredicate {

    @Override

    public boolean apply(@Nullable PredicateKey input) {

        if (!ENABLED.get()) {

            return true;

        }

        // 还是获取区域配置,如是使用的 Nacos 直接返回true

        String serverZone = input.getServer().getZone();

        if (serverZone == null) {

            // there is no zone information from the server, we do not want to filter

            // out this server

            return true;

        }

        // 区域高可用判断

        ...

    }

}

扩展: ServerList 维护

初始化ServerList

在上文 6.创建LoadBalancer 的依赖要素,中 ServerList 目标服务的实例实例表,具体服务发现客户端实现。我们来看下 Nacos 的实现

public class NacosServerList extends AbstractServerList<NacosServer> {

    @Override

    public List<NacosServer> getInitialListOfServers() {

        return getServers();

    }

    @Override

    public List<NacosServer> getUpdatedListOfServers() {

        return getServers();

    }

    private List<NacosServer> getServers() {

        String group = discoveryProperties.getGroup();

        //调用nacos-sdk 查询实例列表

        List<Instance> instances = discoveryProperties.namingServiceInstance()

                .selectInstances(serviceId, group, true);

        // 类型转换

        return instancesToServerList(instances);

    }

}

更新ServerListUpdater

ServerList 初始化后更新操作通过 PollingServerListUpdater

public class PollingServerListUpdater implements ServerListUpdater {

    @Override

    public synchronized void start(final UpdateAction updateAction) {

        // 更新任务 交给updateAction 具体实现

        final Runnable wrapperRunnable = () -> {

            updateAction.doUpdate();

            lastUpdated = System.currentTimeMillis();

        };

        // 开启后台线程定时执行  updateAction

        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(

                wrapperRunnable,

                initialDelayMs,

                refreshIntervalMs,

                TimeUnit.MILLISECONDS

        );

    }

}

updateAction 实现

public void doUpdate() {

DynamicServerListLoadBalancer.this.updateListOfServers();

}

public class PollingServerListUpdater implements ServerListUpdater {

    public void updateListOfServers() {

        List<T> servers = new ArrayList();


        // 调用NacosServiceList 获取全部服务列表

        servers = this.serverListImpl.getUpdatedListOfServers();


        // 如果配置实例过滤器在执行过滤

        if (this.filter != null) {

            servers = this.filter.getFilteredListOfServers((List)servers);

        }


        // 更新LoadBalancer 服务列表

        this.updateAllServerList((List)servers);

    }

}

扩展: Server 状态维护

LoadBalancer 初始构造时会触发 setupPingTask()

public BaseLoadBalancer() {

  this.name = DEFAULT_NAME;

  this.ping = null;

  setRule(DEFAULT_RULE);

  // 开启ping 检查任务

  setupPingTask();

  lbStats = new LoadBalancerStats(DEFAULT_NAME);

}

setupPingTask

void setupPingTask() {

  // 是否可以ping, 默认的DummyPing 直接 跳过不执行

  if (canSkipPing()) {

    return;

  }

  // 执行PingTask

  lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000);

  // 开启任务

  new BaseLoadBalancer.Pinger(pingStrategy).runPinger();

}

SerialPingStrategy 串行执行逻辑

// 串行调度执行 Iping 逻辑

private static class SerialPingStrategy implements IPingStrategy {

  @Override

  public boolean[] pingServers(IPing ping, Server[] servers) {

    int numCandidates = servers.length;

    boolean[] results = new boolean[numCandidates];

    for (int i = 0; i < numCandidates; i++) {

      results[i] = false; /* Default answer is DEAD. */

      if (ping != null) {

        results[i] = ping.isAlive(servers[i]);

      }

    }

    return results;

  }

}

调用url 判断可用性

public class PingUrl implements IPing {

    public boolean isAlive(Server server) {

        urlStr = urlStr + server.getId();

        urlStr = urlStr + this.getPingAppendString();

        boolean isAlive = false;

        HttpClient httpClient = new DefaultHttpClient();

        HttpUriRequest getRequest = new HttpGet(urlStr);

        String content = null;

        HttpResponse response = httpClient.execute(getRequest);

        content = EntityUtils.toString(response.getEntity());

        isAlive = response.getStatusLine().getStatusCode() == 200;

        return isAlive;

    }

}

扩展: RibbonClient 懒加载处理

由上文可知,默认情况下 Ribbon 在第一次请求才会去创建LoadBalancer,这种懒加载机制会导致服务启动后,第一次调用服务延迟问题,甚至在整合 断路器(hystrix)等出现超时熔断 。

为了解决这个问题,我们会配置 Ribbon 的饥饿加载

ribbon:

  eager-load:

    clients:

      - provider

RibbonApplicationContextInitializer 服务启动后自动调用 工厂提前创建需要的ribbon clients

public class RibbonApplicationContextInitializer

        implements ApplicationListener<ApplicationReadyEvent> {

    private final List<String> clientNames;

    protected void initialize() {

        if (clientNames != null) {

            for (String clientName : clientNames) {

                this.springClientFactory.getContext(clientName);

            }

        }

    }

    @Override

    public void onApplicationEvent(ApplicationReadyEvent event) {

        initialize();

    }

}

以上内容都是我自己的一些感想,分享出来欢迎大家指正,顺便求一波关注,有想法的伙伴可以评论或者私信我哦~

作者:冷冷gg

链接:https://juejin.im/post/5e5c598551882549052f49e4

来源:掘金

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容