天天看點

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

用戶端和 dashboard 互動

  • sentinel-transport 三個子工程,common 是基礎包和接口定義
  • 阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題
  • 若用戶端要接入 dashboard,可以使用 netty-http 或 simple-http 中的一個。為何不直接使用 Netty,而要同時提供 http 選項?

因為你不一定使用 Java 來實作 dashboard,如果使用其他語言,使用 http 協定比較容易适配。

下面我們隻介紹 http 的使用,首先,添加 simple-http 依賴:

<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-transport-simple-http</artifactId>
   <version>1.6.3</version>
</dependency>      

然後在應用啟動參數中添加 dashboard 伺服器位址,同時可以指定目前應用的名稱:

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080 
    -Dproject.name=sentinel-learning      

這個時候我們打開 dashboard 是看不到這個應用的,因為沒有注冊。

當我們在第一次使用 Sentinel 以後,Sentinel 會自動注冊。

下面帶大家看看過程是怎樣的。首先,我們在使用 Sentinel 的時候會調用 SphU#entry:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

這裡使用了 Env 類,其實就是這個類做的事情:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

進到 InitExecutor.doInit 方法:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

這裡使用 SPI 加載 InitFunc 的實作,加載了

  • CommandCenterInitFunc 類

    用戶端啟動的接口服務,提供給 dashboard 查詢資料和規則設定使用

  • HeartbeatSenderInitFunc 類

    用于用戶端主動發送心跳資訊給 dashboard

HeartbeatSenderInitFunc#init

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

定時器,以一定的間隔不斷發送心跳資訊到 dashboard 應用

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

dashboard 有了這些資訊,就可以對應用進行規則設定、到應用拉取資料用于頁面展示等。

Sentinel 在用戶端并未使用第三方 http 包,而是自己基于 JDK 的 Socket 和 ServerSocket 接口實作了簡單的用戶端和服務端,主要也是為了不增加依賴。

Sentinel 中秒級 QPS 的統計問題

Sentinel 統計了 分 和 秒 兩個次元資料:

1、對于 分 來說,一輪是 60 秒,分為 60 個時間視窗,每個時間視窗是 1 秒

2、對于 秒 來說,一輪是 1 秒,分為 2 個時間視窗,每個時間視窗是 0.5 秒

如果我們用上面介紹的統計分次元的 BucketLeapArray 來統計秒次元資料可以嗎?不行,因為會不準确。

設想一個場景,我們的一個資源,通路的 QPS 穩定是 10,假設請求是均勻分布的,在相對時間 0.0 - 1.0 秒區間,通過了 10 個請求,我們在 1.1 秒的時候,觀察到的 QPS 可能隻有 5,因為此時第一個時間視窗被重置了,隻有第二個時間視窗有值。

是以,我們可以知道,如果用 BucketLeapArray 來實作,會有 0~50% 的資料誤差,這肯定是不能接受的。

那能不能增加視窗的數量來降低誤差到一個合理的範圍内呢?這個大家可以思考一下,考慮一下它對于性能是否有較大的損失。

StatisticNode 源碼,對于秒次元資料統計,Sentinel 使用下面的構造方法:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

OccupiableBucketLeapArray 的 newEmptyBucket 和 resetWindowTo 這兩個方法和 BucketLeapArray 有點不一樣,也就是在重置的時候,它不是直接重置成 0。

這個類裡面的 borrowArray 做了一些事情,它是 FutureBucketLeapArray 的執行個體,這個類和前面接觸的 BucketLeapArray 差不多,但是加了一個 Future 單詞。它和 BucketLeapArray 唯一的不同是,重寫了下面這個方法:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

若按照這種定義,在調用 values() 方法的時候,所有的 2 個視窗都是過期的,将得不到任何的值。可以判斷,給這個數組添加值的時候,使用的時間應該不是目前時間,而是一個未來的時間點。

回到 OccupiableBucketLeapArray 類,重置使用了 borrowArray 的值:

當主線到達某個時間視窗的時候,如果發現目前時間視窗是過期的,會重置這個視窗

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

再看 borrowArray 中的值是怎麼進來的。

我們很容易可以找到,隻可能通過這裡的 addWaiting 方法設定:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

接下來,我們找這個方法被哪裡調用了,隻有 DefaultController 類中有調用。

這個類是流控中的 “快速失敗” 規則控制器,我們簡單看一下代碼:

阿裡Sentinel核心源碼解析-責任鍊模式最佳實踐(下)用戶端和 dashboard 互動Sentinel 中秒級 QPS 的統計問題

OccupiableBucketLeapArray

Occupiable 這裡代表可以被預占的意思,結合上面 DefaultController 的源碼,可以知道它原來是用來滿足 prioritized 類型的資源的,我們可以認為這類請求有較高的優先級。如果 QPS 達到門檻值,這類資源通常不能用快速失敗傳回, 而是讓它去預占未來的 QPS 容量。

當然,令人失望的是,這裡根本沒有解開 QPS 是怎麼準确計算的這個問題。

下面證明 Sentinel 的秒次元的 QPS 統計是不準确的

public static void main(String[] args) {
    // 下面幾行代碼設定了 QPS 門檻值是 100
    FlowRule rule = new FlowRule("test");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(100);
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
    List<FlowRule> list = new ArrayList<>();
    list.add(rule);
    FlowRuleManager.loadRules(list);

    // 先通過一個請求,讓 clusterNode 先建立起來
    try (Entry entry = SphU.entry("test")) {
    } catch (BlockException e) {
    }

    // 起一個線程一直列印 qps 資料
    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                System.out.println(ClusterBuilderSlot.getClusterNode("test").passQps());
            }
        }
    }).start();

    while (true) {
        try (Entry entry = SphU.entry("test")) {
            Thread.sleep(5);
        } catch (BlockException e) {
            // ignore
        } catch (InterruptedException e) {
            // ignore
        }
    }
}      

然後觀察下輸出,QPS 資料在 50~100 這個區間一直變化,印證秒級 QPS 統計極度不準确。

參考