Sentinel 源碼學習

来源:https://www.cnblogs.com/cjsblog/p/18020907
-Advertisement-
Play Games

引入依賴 <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.7</version> </dependency> 基本用法 try (Entry ent ...


引入依賴

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.7</version>
</dependency>

基本用法

try (Entry entry = SphU.entry("HelloWorld")) {
    // 被保護的邏輯
    System.out.println("hello world");
} catch (BlockException ex) {
    // 處理被流控的邏輯
    System.out.println("blocked!");
}

接下來,閱讀源碼,我們從SphU.entry()開始 

每個SphU#entry()將返回一個Entry。這個類維護了當前調用的一些信息:

  • createTime :這個entry的創建時間,用於響應時間統計
  • current Node :在當前上下文中的資源的統計
  • origin Node :原始節點的統計
  • ResourceWrapper :資源名稱

CtSph#entryWithPriority()方法就是整個流控的基本流程:

1、首先,獲取當前線程上下文,如果為空,則創建一個

2、然後,查找處理器鏈

3、最後,依次執行處理器

這是一個典型的責任鏈

接下來,挨個來看,首先看一下上下文。上下文是一個線程局部變數  ThreadLocal<Context>

如果當前線程還沒有上下文,則創建一個

有了Context之後,接下來查找處理器

這些功能插槽(slot chain)有不同的職責:

  • NodeSelectorSlot :負責收集資源的路徑,並將這些資源的調用路徑,以樹狀結構存儲起來,用於根據調用路徑來限流降級;
  • ClusterBuilderSlot :用於存儲資源的統計信息以及調用者信息,例如該資源的 RT, QPS, thread count 等等,這些信息將用作為多維度限流,降級的依據;
  • StatisticSlot :用於記錄、統計不同緯度的 runtime 指標監控信息;
  • FlowSlot :用於根據預設的限流規則以及前面 slot 統計的狀態,來進行流量控制;
  • AuthoritySlot :根據配置的黑白名單和調用來源信息,來做黑白名單控制;
  • DegradeSlot :通過統計信息以及預設的規則,來做熔斷降級;
  • SystemSlot :通過系統的狀態,例如 load1 等,來控制總的入口流量;

到這裡為止,資源有了,上下文有了,處理器鏈有了,於是,接下來就可以對資源應用所有的處理器了

關於功能插槽的學習就先到這裡,下麵補充一個知識點:Node

Node 用於保存資源的實時統計信息

StatisticNode 保存三種實時統計指標:

  1. 秒級指標
  2. 分鐘級指標
  3. 線程數

DefaultNode 用於保存特定上下文中特定資源名稱的統計信息

EntranceNode 代表調用樹的入口

總之一句話,Node是用於保存統計信息的。那麼,這些指標數據是如何計數的呢?

Sentinel 使用滑動視窗實時記錄和統計資源指標。ArrayMetric背後的滑動視窗基礎結構是LeapArray。

下麵重點看一下StatisticNode

StatisticNode是用於實時統計的處理器插槽。在進入這個槽位時,需要分別計算以下信息:

  • ClusterNode :該資源ID的集群節點統計信息總和
  • Origin node :來自不同調用者/起源的集群節點的統計信息
  • DefaultNode :特定上下文中特定資源名稱的統計信息
  • 最後,是所有入口的總和統計

private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
    long timeId = timeMillis / windowLengthInMs;
    // Calculate current index so we can map the timestamp to the leap array.
    return (int)(timeId % array.length());
}

protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
    return timeMillis - timeMillis % windowLengthInMs;
}

/**
 * Get bucket item at provided timestamp.
 *
 * @param timeMillis a valid timestamp in milliseconds
 * @return current bucket item at provided timestamp if the time is valid; null if time is invalid
 */
public WindowWrap<T> currentWindow(long timeMillis) {
    if (timeMillis < 0) {
        return null;
    }

    int idx = calculateTimeIdx(timeMillis);
    // Calculate current bucket start time.
    long windowStart = calculateWindowStart(timeMillis);

    /*
     * Get bucket item at given time from the array.
     *
     * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
     * (2) Bucket is up-to-date, then just return the bucket.
     * (3) Bucket is deprecated, then reset current bucket.
     */
    while (true) {
        WindowWrap<T> old = array.get(idx);
        if (old == null) {
            /*
             *     B0       B1      B2    NULL      B4
             * ||_______|_______|_______|_______|_______||___
             * 200     400     600     800     1000    1200  timestamp
             *                             ^
             *                          time=888
             *            bucket is empty, so create new and update
             *
             * If the old bucket is absent, then we create a new bucket at {@code windowStart},
             * then try to update circular array via a CAS operation. Only one thread can
             * succeed to update, while other threads yield its time slice.
             */
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            if (array.compareAndSet(idx, null, window)) {
                // Successfully updated, return the created bucket.
                return window;
            } else {
                // Contention failed, the thread will yield its time slice to wait for bucket available.
                Thread.yield();
            }
        } else if (windowStart == old.windowStart()) {
            /*
             *     B0       B1      B2     B3      B4
             * ||_______|_______|_______|_______|_______||___
             * 200     400     600     800     1000    1200  timestamp
             *                             ^
             *                          time=888
             *            startTime of Bucket 3: 800, so it's up-to-date
             *
             * If current {@code windowStart} is equal to the start timestamp of old bucket,
             * that means the time is within the bucket, so directly return the bucket.
             */
            return old;
        } else if (windowStart > old.windowStart()) {
            /*
             *   (old)
             *             B0       B1      B2    NULL      B4
             * |_______||_______|_______|_______|_______|_______||___
             * ...    1200     1400    1600    1800    2000    2200  timestamp
             *                              ^
             *                           time=1676
             *          startTime of Bucket 2: 400, deprecated, should be reset
             *
             * If the start timestamp of old bucket is behind provided time, that means
             * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.
             * Note that the reset and clean-up operations are hard to be atomic,
             * so we need a update lock to guarantee the correctness of bucket update.
             *
             * The update lock is conditional (tiny scope) and will take effect only when
             * bucket is deprecated, so in most cases it won't lead to performance loss.
             */
            if (updateLock.tryLock()) {
                try {
                    // Successfully get the update lock, now we reset the bucket.
                    return resetWindowTo(old, windowStart);
                } finally {
                    updateLock.unlock();
                }
            } else {
                // Contention failed, the thread will yield its time slice to wait for bucket available.
                Thread.yield();
            }
        } else if (windowStart < old.windowStart()) {
            // Should not go through here, as the provided time is already behind.
            return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
        }
    }
}

現在,有2個視窗,每個視窗500ms,2個視窗總共1000ms

假設,當前時間戳是1200ms,那麼 (1200 / 500) % 2 = 0, 1200 - 1200 % 500 = 1000

這個時候,如果0這個位置沒有視窗,則創建一個新的視窗,新視窗的視窗開始時間是1000ms

如果0這個位置有視窗,則繼續判斷舊視窗的視窗開始時間是否為1000ms,如果是,則表示視窗沒有過期,直接返回該視窗。如果舊視窗的開始時間小於1000ms,則表示舊視窗過期了,於是重置舊視窗的統計數據,重新設置視窗開始時間(PS:相當於將視窗向後移動)

視窗(桶)數據保存在MetricBucket中

總結一下:

1、每個線程過來之後,創建上下文,然後依次經過各個功能插槽

2、每個資源都有自己的處理器鏈,也就是說多次訪問同一個資源時,用的同一套處理器鏈(插槽)

3、Node相當於是一個載體,用於保存資源的實時統計信息

4、第一次進入插槽後,創建一個新Node,後面再補充Node的信息;第二次進入的時候,由於上下文的名稱都是一樣的,所以不會再創建Node,而是用之前的Node,也就是還是在之前的基礎上記錄統計信息。可以這樣理解,每個DefaultNode就對應一個特定的資源。

5、StatisticNode中保存三種類型的指標數據:每秒的指標數據,每分鐘的指標數據,線程數。

6、指標數據統計採用滑動視窗,利用當前時間戳和視窗長度計算數據應該落在哪個視窗數組區間,通過視窗開始時間判斷視窗是否過期。實際數據保存在MetricBucket中

最後,千言萬語彙聚成這張原理圖

NodeSelectorSlot構造調用鏈路,ClusterBuilderSlot構造統計節點,StatisticSlot利用滑動視窗進行指標統計,然後是流量控制

 

參考文檔

https://sentinelguard.io/zh-cn/docs/quick-start.html

https://sentinelguard.io/zh-cn/docs/basic-implementation.html

https://sentinelguard.io/zh-cn/docs/dashboard.html

https://blog.csdn.net/xiaolyuh123/article/details/107937353

https://www.cnblogs.com/magexi/p/13124870.html

https://www.cnblogs.com/mrxiaobai-wen/p/14212637.html

https://www.cnblogs.com/taromilk/p/11750962.html

https://www.cnblogs.com/taromilk/p/11751000.html

https://www.cnblogs.com/wekenyblog/p/17519276.html

https://javadoop.com/post/sentinel

https://www.cnblogs.com/cuzzz/p/17413429.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Miniconda是Anaconda的簡化版, 可以管理多個Python版本的環境. 實際使用的話, 占用的空間不會很小, 我跑一些正常的應用後, 安裝目錄占用空間4.3GB, 安裝建議要預留10到20G的空間. 安裝 Miniconda 下載安裝包 https://docs.anaconda.co ...
  • 美團面試:Kafka如何處理百萬級消息隊列? 在今天的大數據時代,處理海量數據已成為各行各業的標配。特別是在消息隊列領域,Apache Kafka 作為一個分散式流處理平臺,因其高吞吐量、可擴展性、容錯性以及低延遲的特性而廣受歡迎。但當面對真正的百萬級甚至更高量級的消息處理時,如何有效地利用 Kaf ...
  • 摘要 我們報告了 GPT-4 的開發,這是一個大規模、多模態的模型,可以接受圖像和文本輸入,並生成文本輸出。雖然在許多現實場景中不如人類,但 GPT-4 在各種專業和學術基準測試中表現出與人類水平相當的性能,包括在模擬的律師資格考試中取得了約前10%的考生得分。 GPT-4 是基於 Transfor ...
  • 虛擬線程(Virtual Threads)是 Java 21 所有新特性中最為吸引人的內容,它可以大大來簡化和增強Java應用的併發性。但是,隨著這些變化而來的是如何最好地管理此吞吐量的問題。本文,就讓我們看一下開發人員在使用虛擬線程時,應該如何管理吞吐量。 在大多數情況下,開發人員不需要自己創建虛 ...
  • 首先,跨域的域是什麼? 跨域的英文是:Cross-Origin。 Origin 中文含義為:起源,源頭,出生地。 在跨域中,"域"指的是一個 Web 資源(比如網頁、腳本、圖片等)的源頭。 包括該資源的協議、主機名、埠號。 在同源策略中,如果兩個資源的域相同,則它們屬於同一域,可以自由進行交互和共 ...
  • 通過使用Python編程語言,編寫腳本來自動化Excel和CSV之間的轉換過程,可以批量處理大量文件,定期更新數據,並集成轉換過程到自動化工作流程中。本文將介紹如何使用第三方庫Spire.XLS for Python 實現: 使用Python將Excel轉為CSV 使用Python 將CSV轉為Ex ...
  • 多年不用PageHelper了,最近新入職的公司,採用了此工具集成的框架,作為一個獨立緊急項目開發的基礎。項目開發起來,還是手到擒來的,但是沒想到,最終測試的時候,深深的給我上了一課。 我的項目發生了哪些奇葩現象? 一切的問題都要從我接受的項目開始說起, 在開發這個項目的過程中,發生了各種奇葩的事情 ...
  • OOM 幾乎是筆者工作中遇到的線上 bug 中最常見的,一旦平時正常的頁面線上上出現頁面崩潰或者服務無法調用,查看伺服器日誌後你很可能會看到“Caused by: java.lang.OutOfMlemoryError: Java heap space” 這樣的提示,那麼毫無疑問表示的是 Java ... ...
一周排行
    -Advertisement-
    Play Games
  • 一個自定義WPF窗體的解決方案,借鑒了呂毅老師的WPF製作高性能的透明背景的異形視窗一文,併在此基礎上增加了滑鼠穿透的功能。可以使得透明窗體的滑鼠事件穿透到下層,在下層窗體中響應。 ...
  • 在C#中使用RabbitMQ做個簡單的發送郵件小項目 前言 好久沒有做項目了,這次做一個發送郵件的小項目。發郵件是一個比較耗時的操作,之前在我的個人博客裡面回覆評論和友鏈申請是會通過發送郵件來通知對方的,不過當時只是簡單的進行了非同步操作。 那麼這次來使用RabbitMQ去統一發送郵件,我的想法是通過 ...
  • 當你使用Edge等瀏覽器或系統軟體播放媒體時,Windows控制中心就會出現相應的媒體信息以及控制播放的功能,如圖。 SMTC (SystemMediaTransportControls) 是一個Windows App SDK (舊為UWP) 中提供的一個API,用於與系統媒體交互。接入SMTC的好 ...
  • 最近在微軟商店,官方上架了新款Win11風格的WPF版UI框架【WPF Gallery Preview 1.0.0.0】,這款應用引入了前沿的Fluent Design UI設計,為用戶帶來全新的視覺體驗。 ...
  • 1.簡單使用實例 1.1 添加log4net.dll的引用。 在NuGet程式包中搜索log4net並添加,此次我所用版本為2.0.17。如下圖: 1.2 添加配置文件 右鍵項目,添加新建項,搜索選擇應用程式配置文件,命名為log4net.config,步驟如下圖: 1.2.1 log4net.co ...
  • 之前也分享過 Swashbuckle.AspNetCore 的使用,不過版本比較老了,本次演示用的示例版本為 .net core 8.0,從安裝使用開始,到根據命名空間分組顯示,十分的有用 ...
  • 在 Visual Studio 中,至少可以創建三種不同類型的類庫: 類庫(.NET Framework) 類庫(.NET 標準) 類庫 (.NET Core) 雖然第一種是我們多年來一直在使用的,但一直感到困惑的一個主要問題是何時使用 .NET Standard 和 .NET Core 類庫類型。 ...
  • WPF的按鈕提供了Template模板,可以通過修改Template模板中的內容對按鈕的樣式進行自定義。結合資源字典,可以將自定義資源在xaml視窗、自定義控制項或者整個App當中調用 ...
  • 實現了一個支持長短按得按鈕組件,單擊可以觸發Click事件,長按可以觸發LongPressed事件,長按鬆開時觸發LongClick事件。還可以和自定義外觀相結合,實現自定義的按鈕外形。 ...
  • 一、WTM是什麼 WalkingTec.Mvvm框架(簡稱WTM)最早開發與2013年,基於Asp.net MVC3 和 最早的Entity Framework, 當初主要是為瞭解決公司內部開發效率低,代碼風格不統一的問題。2017年9月,將代碼移植到了.Net Core上,併進行了深度優化和重構, ...