1. 程式人生 > >【一起學原始碼-微服務】Ribbon 原始碼四:進一步探究Ribbon的IRule和IPing

【一起學原始碼-微服務】Ribbon 原始碼四:進一步探究Ribbon的IRule和IPing

前言

前情回顧

上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進行定時更新Eureka登錄檔資訊到BaseLoadBalancer中,預設30s排程一次。

本講目錄

我們知道Ribbon主要是由3個元件組成的:

  1. ILoadBalancer
  2. IRule
  3. IPing

其中ILoadBalancer前面我們已經分析過了,接下來我們一起看看IRuleIPing

中的具體實現。

目錄如下:

  1. 負載均衡預設Server選擇邏輯
  2. Ribbon實際執行http請求邏輯
  3. Ribbon中ping機制原理
  4. Ribbon中其他IRule負載演算法初探

說明

原創不易,如若轉載 請標明來源!

部落格地址:一枝花算不算浪漫
微信公眾號:壹枝花算不算浪漫

原始碼分析

負載均衡預設Server選擇邏輯

還記得我們上一講說過,在Ribbon初始化過程中,預設的IRuleZoneAvoidanceRule,這裡我們可以通過debug看看,從RibbonLoadBalancerClient.getServer() 一路往下跟,這裡直接看debug結果:

然後我們繼續跟ZoneAvoidanceRule.choose()

方法:

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
   
    /**
     * Method that provides an instance of {@link AbstractServerPredicate} to be used by this class.
     * 
     */
    public abstract AbstractServerPredicate getPredicate();
        
    /**
     * Get a server by calling {@link AbstractServerPredicate#chooseRandomlyAfterFiltering(java.util.List, Object)}.
     * The performance for this method is O(n) where n is number of servers to be filtered.
     */
    @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;
        }       
    }
}

這裡是呼叫的ZoneAvoidanceRule的父類中的choose()方法,首先是拿到對應的ILoadBalancer,然後拿到對應的serverList資料,接著呼叫chooseRoundRobinAfterFiltering()方法,繼續往後跟:

public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {

    public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
        return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
    }

    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            int next = (current + 1) % modulo;
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }
}

到了這裡可以看到incrementAndGetModulo()方法就是處理serverList輪詢的演算法,這個和RoudRobinRule中採用的是一樣的演算法,這個演算法大家可以品一品,我這裡也會畫個圖來說明下:

看了圖=中的例子估計大家都會明白了,這個演算法就是依次輪詢。這個演算法寫的很精簡。

Ribbon實際執行http請求邏輯

我們上面知道,我們按照輪詢的方式從serverList取到一個server後,那麼怎麼把之前原有的類似於:http://ServerA/sayHello/wangmeng中的ServerA給替換成請求的ip資料呢?

接著我們繼續看RibbonLoadBalancerClient.execute()方法,這個裡面request.apply()會做一個serverName的替換邏輯。

最後可以一步步跟到RibbonLoadBalancerClient.reconstructURI(),這個方法是把請求自帶的getURI方法給替換了,我們最後檢視context.reconstructURIWithServer() 方法,debug結果如圖,這個裡面會一步步把對應的請求url給拼接起來:

Ribbon中ping機制原理

我們知道 Ribbon還有一個重要的元件就是ping機制,通過上一講Ribbon的初始化我們知道,預設的IPing實現類為:NIWSDiscoveryPing,我們可以檢視其中的isAlive()方法:

public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {
            
        BaseLoadBalancer lb = null; 
        

        public NIWSDiscoveryPing() {
        }
        
        public BaseLoadBalancer getLb() {
            return lb;
        }

        /**
         * Non IPing interface method - only set this if you care about the "newServers Feature"
         * @param lb
         */
        public void setLb(BaseLoadBalancer lb) {
            this.lb = lb;
        }

        public boolean isAlive(Server server) {
            boolean isAlive = true;
            if (server!=null && server instanceof DiscoveryEnabledServer){
                DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;                
                InstanceInfo instanceInfo = dServer.getInstanceInfo();
                if (instanceInfo!=null){                    
                    InstanceStatus status = instanceInfo.getStatus();
                    if (status!=null){
                        isAlive = status.equals(InstanceStatus.UP);
                    }
                }
            }
            return isAlive;
        }

        @Override
        public void initWithNiwsConfig(
                IClientConfig clientConfig) {
        }
        
}

這裡就是獲取到DiscoveryEnabledServer對應的註冊資訊是否為UP狀態。那麼 既然有個ping的方法,肯定會有方法進行排程的。

我們可以檢視isAlive()呼叫即可以找到排程的地方。
BaseLoadBalancer建構函式中會呼叫setupPingTask()方法,進行排程:

protected int pingIntervalSeconds = 10;

void setupPingTask() {
    if (canSkipPing()) {
        return;
    }
    if (lbTimer != null) {
        lbTimer.cancel();
    }
    lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
            true);
    lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
    forceQuickPing();
}

這裡pingIntervalSecondsBaseLoadBalancer中定義的是10s,但是在initWithConfig()方法中,通過傳入的時間覆蓋了原本的10s,這裡實際的預設時間是30s。如下程式碼:

我們也可以通過debug來看看:

可能大家好奇為什麼要單獨截圖來說這個事,其實是因為網上好多部落格講解都是錯的,都寫的是ping預設排程時間為10s,想必他們都是沒有真正debug過吧。

還是那句話,原始碼出真知。

Ribbon中其他IRule負載演算法初探

  1. RoundRobinRule:系統內建的預設負載均衡規範,直接round robin輪詢,從一堆server list中,不斷的輪詢選擇出來一個server,每個server平攤到的這個請求,基本上是平均的

    這個演算法,說白了是輪詢,就是一臺接著一臺去請求,是按照順序來的

  2. AvailabilityFilteringRule:這個rule就是會考察伺服器的可用性

    如果3次連線失敗,就會等待30秒後再次訪問;如果不斷失敗,那麼等待時間會不斷變長
    如果某個伺服器的併發請求太高了,那麼會繞過去,不再訪問

    這裡先用round robin演算法,輪詢依次選擇一臺server,如果判斷這個server是存活的可用的,如果這臺server是不可以訪問的,那麼就用round robin演算法再次選擇下一臺server,依次迴圈往復,10次。

  3. WeightedResponseTimeRule:帶著權重的,每個伺服器可以有權重,權重越高優先訪問,如果某個伺服器響應時間比較長,那麼權重就會降低,減少訪問

  4. ZoneAvoidanceRule:根據機房和伺服器來進行負載均衡,說白了,就是機房的意思,看了原始碼就是知道了,這個就是所謂的spring cloud ribbon環境中的預設的Rule

  5. BestAvailableRule:忽略那些請求失敗的伺服器,然後儘量找併發比較低的伺服器來請求

總結

到了這裡 Ribbon相關的就結束了,對於Ribbon登錄檔拉取及更新邏輯這裡也梳理下,這裡如果Ribbon儲存的登錄檔資訊有宕機的情況,這裡最多4分鐘才能感知到,所以spring cloud還有一個服務熔斷的機制,這個後面也會講到。

申明

本文章首發自本人部落格:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!

感興趣的小夥伴可關注個人公眾號:壹枝花算不算浪漫

相關推薦

一起原始碼-服務Ribbon 原始碼Ribbon概念理解及Demo除錯

前言 前情回顧 前面文章已經梳理清楚了Eureka相關的概念及原始碼,接下來開始研究下Ribbon的實現原理。 我們都知道Ribbon在spring cloud中擔當負載均衡的角色, 當兩個Eureka Client互相呼叫的時候,Ribbon能夠做到呼叫時的負載,保證多節點的客戶端均勻接收請求。(這個有點類

一起原始碼-服務Ribbon 原始碼通過Debug找出Ribbon初始化流程及ILoadBalancer原理分析

前言 前情回顧 上一講講了Ribbon的基礎知識,通過一個簡單的demo看了下Ribbon的負載均衡,我們在RestTemplate上加了@LoadBalanced註解後,就能夠自動的負載均衡了。 本講目錄 這一講主要是繼續深入RibbonLoadBalancerClient和Ribbon+Eureka整合的

一起原始碼-服務Ribbon 原始碼Ribbon與Eureka整合原理分析

前言 前情回顧 上一篇講了Ribbon的初始化過程,從LoadBalancerAutoConfiguration 到RibbonAutoConfiguration 再到RibbonClientConfiguration,我們找到了ILoadBalancer預設初始化的物件等。 本講目錄 這一講我們會進一步往下

一起原始碼-服務Ribbon 原始碼進一步探究Ribbon的IRuleIPing

前言 前情回顧 上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進

一起原始碼-服務Ribbon原始碼Ribbon原始碼解讀彙總篇~

前言 想說的話 【一起學原始碼-微服務-Ribbon】專欄到這裡就已經全部結束了,共更新四篇文章。 Ribbon比較小巧,這裡是直接 讀的spring cloud 內嵌封裝的版本,裡面的各種configuration確實有點繞,不過看看第三講Ribbon初始化的過程總結圖就會清晰很多。 緊接著會繼續整理學習F

一起原始碼-服務Feign 原始碼Feign結合Ribbon實現負載均衡的原理分析

前言 前情回顧 上一講我們已經知道了Feign的工作原理其實是在專案啟動的時候,通過JDK動態代理為每個FeignClinent生成一個動態代理。 動態代理的資料結構是:ReflectiveFeign.FeignInvocationHandler。其中包含target(裡面是serviceName等資訊)和d

一起原始碼-服務Feign 原始碼原始碼初探,通過Demo Debug Feign原始碼

前言 前情回顧 上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進

一起原始碼-服務Feign 原始碼Feign動態代理構造過程

前言 前情回顧 上一講主要看了@EnableFeignClients中的registerBeanDefinitions()方法,這裡面主要是 將EnableFeignClients註解對應的配置屬性注入,將FeignClient註解對應的屬性注入。 最後是生成FeignClient對應的bean,注入到Spr

一起原始碼-服務Hystrix 原始碼Hystrix基礎原理與Demo搭建

說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一個系列文章講解了Feign的原始碼,主要是Feign動態代理實現的原理,及配合Ribbon實現負載均衡的機制。 這裡我們講解一個新的元件Hystrix,也是和Fe

一起原始碼-服務Hystrix 原始碼Hystrix核心流程Hystix非降級邏輯流程梳理

說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一講我們講了配置了feign.hystrix.enabled=true之後,預設的Targeter就會構建成HystrixTargter, 然後通過對應的Hystr

一起原始碼-服務Hystrix 原始碼Hystrix核心流程Hystix降級、熔斷等原理剖析

說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一講我們講解了Hystrix在配合feign的過程中,一個正常的請求邏輯該怎樣處理,這裡涉及到執行緒池的建立、HystrixCommand的執行等邏輯。 如圖所示:

一起原始碼-服務Nexflix Eureka 原始碼EurekaServer啟動之配置檔案載入以及面向介面的配置項讀取

前言 上篇文章已經介紹了 為何要讀netflix eureka原始碼了,這裡就不再概述,下面開始正式原始碼解讀的內容。 如若轉載 請標明來源:一枝花算不算浪漫 程式碼總覽 還記得上文中,我們通過web.xml找到了eureka server入口的類EurekaBootStrap,這裡我們就先來簡單地看下: /

一起原始碼-服務Nexflix Eureka 原始碼EurekaServer啟動之EurekaServer上下文EurekaClient建立

前言 上篇文章已經介紹了 Eureka Server 環境和上下文初始化的一些程式碼,其中重點講解了environment初始化使用的單例模式,以及EurekaServerConfigure基於介面對外暴露配置方法的設計方式。這一講就是講解Eureka Server上下文初始化剩下的內容:Eureka Cli

一起原始碼-服務Nexflix Eureka 原始碼在眼花繚亂的程式碼中,EurekaClient是如何註冊的?

前言 上一講已經講解了EurekaClient的啟動流程,到了這裡已經有6篇Eureka原始碼分析的文章了,看了下之前的文章,感覺程式碼成分太多,會影響閱讀,後面會只擷取主要的程式碼,加上註釋講解。 這一講看的是EurekaClient註冊的流程,當然也是一塊核心,標題為什麼會寫上眼花繚亂呢?關於Eureka

一起原始碼-服務Nexflix Eureka 原始碼通過單元測試來Debug Eureka註冊過程

前言 上一講eureka client是如何註冊的,一直跟到原始碼傳送http請求為止,當時看eureka client註冊時如此費盡,光是找一個regiter的地方就找了半天,那麼client端傳送了http請求給server端,server端是如何處理的呢? 帶著這麼一個疑問 就開始今天原始碼的解讀了。

一起原始碼-服務Nexflix Eureka 原始碼EurekaClient登錄檔抓取 精妙設計分析!

前言 前情回顧 上一講 我們通過單元測試 來梳理了EurekaClient是如何註冊到server端,以及server端接收到請求是如何處理的,這裡最重要的關注點是登錄檔的一個數據結構:ConcurrentHashMap<String, Map<String, Lease<InstanceI

一起原始碼-服務Nexflix Eureka 原始碼服務續約原始碼分析

前言 前情回顧 上一講 我們講解了服務發現的相關邏輯,所謂服務發現 其實就是登錄檔抓取,服務例項預設每隔30s去註冊中心抓取一下注冊表增量資料,然後合併本地登錄檔資料,最後有個hash對比的操作。 本講目錄 今天主要是看下服務續約的邏輯,服務續約就是client端給server端傳送心跳檢測,告訴對方我還活著

一起原始碼-服務Nexflix Eureka 原始碼服務下線及例項摘除,一個client下線到底多久才會被其他例項感知?

前言 前情回顧 上一講我們講了 client端向server端傳送心跳檢查,也是預設每30鍾傳送一次,server端接收後會更新登錄檔的一個時間戳屬性,然後一次心跳(續約)也就完成了。 本講目錄 這一篇有兩個知識點及一個疑問,這個疑問是在工作中真真實實遇到過的。 例如我有服務A、服務B,A、B都註冊在同一個註

一起原始碼-服務Nexflix Eureka 原始碼十一EurekaServer自我保護機制竟然有這麼多Bug?

前言 前情回顧 上一講主要講了服務下線,已經註冊中心自動感知宕機的服務。 其實上一講已經包含了很多EurekaServer自我保護的程式碼,其中還發現了1.7.x(1.9.x)包含的一些bug,但這些問題在master分支都已修復了。 服務下線會將服務例項從登錄檔中刪除,然後放入到recentQueue中,下

一起原始碼-服務Nexflix Eureka 原始碼十二EurekaServer叢集模式原始碼分析

前言 前情回顧 上一講看了Eureka 註冊中心的自我保護機制,以及裡面提到的bug問題。 哈哈 轉眼間都2020年了,這個系列的文章從12.17 一直寫到現在,也是不容易哈,每天持續不斷學習,輸出部落格,這一段時間確實收穫很多。 今天在公司給組內成員分享了Eureka原始碼剖析,反響效果還可以,也算是感覺收