用戶端和 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:
這裡使用了 Env 類,其實就是這個類做的事情:
進到 InitExecutor.doInit 方法:
這裡使用 SPI 加載 InitFunc 的實作,加載了
-
CommandCenterInitFunc 類
用戶端啟動的接口服務,提供給 dashboard 查詢資料和規則設定使用
-
HeartbeatSenderInitFunc 類
用于用戶端主動發送心跳資訊給 dashboard
HeartbeatSenderInitFunc#init
定時器,以一定的間隔不斷發送心跳資訊到 dashboard 應用
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 使用下面的構造方法:
OccupiableBucketLeapArray 的 newEmptyBucket 和 resetWindowTo 這兩個方法和 BucketLeapArray 有點不一樣,也就是在重置的時候,它不是直接重置成 0。
這個類裡面的 borrowArray 做了一些事情,它是 FutureBucketLeapArray 的執行個體,這個類和前面接觸的 BucketLeapArray 差不多,但是加了一個 Future 單詞。它和 BucketLeapArray 唯一的不同是,重寫了下面這個方法:
若按照這種定義,在調用 values() 方法的時候,所有的 2 個視窗都是過期的,将得不到任何的值。可以判斷,給這個數組添加值的時候,使用的時間應該不是目前時間,而是一個未來的時間點。
回到 OccupiableBucketLeapArray 類,重置使用了 borrowArray 的值:
當主線到達某個時間視窗的時候,如果發現目前時間視窗是過期的,會重置這個視窗
再看 borrowArray 中的值是怎麼進來的。
我們很容易可以找到,隻可能通過這裡的 addWaiting 方法設定:
接下來,我們找這個方法被哪裡調用了,隻有 DefaultController 類中有調用。
這個類是流控中的 “快速失敗” 規則控制器,我們簡單看一下代碼:
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 統計極度不準确。
參考