不懂Ribbon原理的可以進來看看哦,分析RibbonClientConfiguration完成了哪些核心初始操作

在這裡插入圖片描述
  本文在前一篇文章的基礎上來繼續分析Ribbon的核心內容。
不懂Ribbon原理的可以進來看看哦,分析SpringBoot自動裝配完成了Ribbon哪些核心操作

RibbonClientConfiguration

  RibbonClientConfiguration是一個非常中的Ribbon配置類,在第一個發起Ribbon請求的時候會完成對應的初始化操作。會完成多個相關的默認設置。

介面 默認實現 描述
IClientConfig DefaultClientConfigImpl 管理配置介面
IRule ZoneAvoidanceRule 均衡策略介面
IPing DummyPing 檢查服務可用性介面
ServerList<Server> ConfigurationBasedServerList 獲取服務列表介面
ILoadBalancer ZoneAwareLoadBalancer 負載均衡介面
ServerListUpdater PollingServerListUpdater 定時更新服務列表介面
ServerIntrospector DefaultServerIntrospector 安全埠介面
@Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, 1000);
        config.set(CommonClientConfigKey.ReadTimeout, 1000);
        config.set(CommonClientConfigKey.GZipPayload, true);
        return config;
    }

    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing());
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerList.class, this.name)) {
            return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.name);
        } else {
            ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
            serverList.initWithNiwsConfig(config);
            return serverList;
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
        return new PollingServerListUpdater(config);
    }

    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));

  在眾多的默認實現中比較重要的是【ILoadBalancer】對象的實現。即【ZoneAwareLoadBalancer】的實現。實現的原理圖為:
請添加圖片描述
在【ZoneAwareLoadBalancer】裡面完成了服務地址動態獲取和服務地址更新定時任務的配置。首先會進入【ZoneAwareLoadBalancer】的構造方法中

public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }

  通過源碼能夠發現會調用父類中的構造方法。

    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        // 繼續調用父類中的方法
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        // 完成相關的初始操作  服務地址獲取和更新
        restOfInit(clientConfig);
    }

  在上面的源碼中我們先繼續跟蹤父類中的方法。

    void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
        this.config = clientConfig;
        String clientName = clientConfig.getClientName();
        this.name = clientName;
        // 設置了定時任務的間隔時間為30秒。
        int pingIntervalTime = Integer.parseInt(""
                + clientConfig.getProperty(
                        CommonClientConfigKey.NFLoadBalancerPingInterval,
                        Integer.parseInt("30")));
        int maxTotalPingTime = Integer.parseInt(""
                + clientConfig.getProperty(
                        CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
                        Integer.parseInt("2")));

        setPingInterval(pingIntervalTime);
        setMaxTotalPingTime(maxTotalPingTime);

        // cross associate with each other
        // i.e. Rule,Ping meet your container LB
        // LB, these are your Ping and Rule guys ...
        setRule(rule);
        setPing(ping);

        setLoadBalancerStats(stats);
        rule.setLoadBalancer(this);
        if (ping instanceof AbstractLoadBalancerPing) {
            ((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
        }
        logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
        boolean enablePrimeConnections = clientConfig.get(
                CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);

        if (enablePrimeConnections) {
            this.setEnablePrimingConnections(true);
            PrimeConnections primeConnections = new PrimeConnections(
                    this.getName(), clientConfig);
            this.setPrimeConnections(primeConnections);
        }
        init();

    }

  在initWithConfig方法中比較中的就是設置了定時任務的間隔時間。然後我們再回到restOfInit方法中。(一起來進階提升吧:463257262)

    void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        // 設置定時任務
        enableAndInitLearnNewServersFeature();
		// 獲取並更新服務地址
        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

  先看enableAndInitLearnNewServersFeature方法

    public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);
    }

  start方法的實現有多種,根據我們的服務選擇對應的選擇即可。比如本地就使用PollingServerListUpdater,如果是Eureka註冊中心就選擇EurekaNotificationServerListUpdater.
請添加圖片描述
以本地為例:

@Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            // 定時任務的  任務體
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        // doUpdate()的方法體要注意
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
			// 設置定時任務 10秒開始第一次檢查,間隔時間是30秒
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }

  此處要注意定時任務的具體內容,以本地為例。
請添加圖片描述
 所以定時任務執行的方法也就是【updateListOfServers】方法,也就是:
請添加圖片描述
emsp; 所以我們繼續來看看【updateListOfServers】方法中的邏輯

    @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
            // 從本地或者Eureka或者Nacos等各個配置中心中獲取對應的服務地址資訊
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        // 更新服務地址資訊
        updateAllServerList(servers);
    }

  上面程式碼中重要的方法是【getUpdatedListOfServers】和【updateAllServerList】,先來看【getUpdatedListOfServers】方法
請添加圖片描述
  查看本地的邏輯,Eureka的自行查看

	@Override
	public List<Server> getUpdatedListOfServers() {
        // 從本地配置中獲取
        String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
        return derive(listOfServers);
	}

  然後就是【updateAllServerList】方法

    protected void updateAllServerList(List<T> ls) {
        // other threads might be doing this - in which case, we pass
        // 通過CAS保證操作的原子性
        if (serverListUpdateInProgress.compareAndSet(false, true)) {
            try {
                for (T s : ls) {
                    s.setAlive(true); // set so that clients can start using these
                                      // servers right away instead
                                      // of having to wait out the ping cycle.
                }
                // 更新服務地址資訊
                setServersList(ls);
                // 強制ping服務地址
                super.forceQuickPing();
            } finally {
                serverListUpdateInProgress.set(false);
            }
        }
    }

以上的操作流程圖為:
請添加圖片描述

好了~【RibbonClientConfiguration】這個配置類的內容就給大家介紹到這裡,歡迎大家一鍵三連!!!

Tags: