sentinel 核心概念

来源:https://www.cnblogs.com/luoxn28/archive/2019/06/28/11100726.html
-Advertisement-
Play Games

編者註:前段時間筆者在團隊內部分享了sentinel原理設計與實現,主要講解了sentinel基礎概念和工作原理,工作原理部分大家聽了基本都瞭解了,但是對於sentinel的幾個概念及其之間的關係還有挺多同學有點模糊的,趁著這幾天比較空,針對sentinel的幾個核心概念,做了一些總結,希望能幫助一 ...


編者註:前段時間筆者在團隊內部分享了sentinel原理設計與實現,主要講解了sentinel基礎概念和工作原理,工作原理部分大家聽了基本都瞭解了,但是對於sentinel的幾個概念及其之間的關係還有挺多同學有點模糊的,趁著這幾天比較空,針對sentinel的幾個核心概念,做了一些總結,希望能幫助一些sentinel初學者理清這些概念之間的關係。

 

PS:本文主要參考sentinel源碼實現和部分官方文檔,建議小伙伴閱讀本文的同時也大致看下官方文檔和源碼,學習效果更好呦 : )  官方文檔講解的其實還是挺詳細的,但是對於這些概念之間的關係可能對於初學者來說還有點不夠。

 

估計挺多小伙伴還不知道Sentinel是個什麼東東,Sentinel是一個以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性的框架。github地址為:https://github.com/alibaba/Sentinel


資源和規則

資源是 Sentinel 的關鍵概念。它可以是 Java 應用程式中的任何內容,例如,由應用程式提供的服務,或由應用程式調用的其它應用提供的服務,甚至可以是一段代碼。只要通過 Sentinel API 定義的代碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作為資源名來標示資源。

圍繞資源的實時狀態設定的規則,可以包括流量控制規則、熔斷降級規則以及系統保護規則。所有規則可以動態實時調整。

 

sentinel中調用SphU或者SphO的entry方法獲取限流資源,不同的是前者獲取限流資源失敗時會拋BlockException異常,後者或捕獲該異常並返回false,二者的實現都是基於CtSph類完成的。簡單的sentinel示例:

 1 Entry entry = null;
 2 try {
 3    entry = SphU.entry(KEY);
 4    System.out.println("entry ok...");
 5 } catch (BlockException e1) {
 6    // 獲取限流資源失敗
 7 } catch (Exception e2) {
 8    // biz exception
 9 } finally {
10    if (entry != null) {
11        entry.exit();
12   }
13 }
14 
15 Entry entry = null;
16 if (SphO.entry(KEY)) {
17    System.out.println("entry ok");
18 } else {
19    // 獲取限流資源失敗
20 }

SphU和SphO二者沒有孰優孰略問題,底層實現是一樣的,根據不同場景選舉合適的一個即可。看了簡單示例之後,一起來看下sentinel中的核心概念,便於理解後續內容。

 

核心概念

Resource

resource是sentinel中最重要的一個概念,sentinel通過資源來保護具體的業務代碼或其他後方服務。sentinel把複雜的邏輯給屏蔽掉了,用戶只需要為受保護的代碼或服務定義一個資源,然後定義規則就可以了,剩下的通通交給sentinel來處理了。並且資源和規則是解耦的,規則甚至可以在運行時動態修改。定義完資源後,就可以通過在程式中埋點來保護你自己的服務了,埋點的方式有兩種:

  • try-catch 方式(通過 SphU.entry(...)),當 catch 到BlockException時執行異常處理(或fallback)

  • if-else 方式(通過 SphO.entry(...)),當返回 false 時執行異常處理(或fallback)

以上這兩種方式都是通過硬編碼的形式定義資源然後進行資源埋點的,對業務代碼的侵入太大,從0.1.1版本開始,sentinel加入了註解的支持,可以通過註解來定義資源,具體的註解為:SentinelResource 。通過註解除了可以定義資源外,還可以指定 blockHandler 和 fallback 方法。

 

在sentinel中具體表示資源的類是:ResourceWrapper ,他是一個抽象的包裝類,包裝了資源的 Name 和EntryType。他有兩個實現類,分別是:StringResourceWrapper 和 MethodResourceWrapper。顧名思義,StringResourceWrapper 是通過對一串字元串進行包裝,是一個通用的資源包裝類,MethodResourceWrapper 是對方法調用的包裝。

 

Context

Context是對資源操作時的上下文環境,每個資源操作(針對Resource進行的entry/exit)必須屬於一個Context,如果程式中未指定Context,會創建name為"sentinel_default_context"的預設Context。一個Context生命周期內可能有多個資源操作,Context生命周期內的最後一個資源exit時會清理該Context,這也預示這整個Context生命周期的結束。Context主要屬性如下:

 1 public class Context {
 2    // context名字,預設名字 "sentinel_default_context"
 3    private final String name;
 4    // context入口節點,每個context必須有一個entranceNode
 5    private DefaultNode entranceNode;
 6    // context當前entry,Context生命周期中可能有多個Entry,所有curEntry會有變化
 7    private Entry curEntry;
 8    // The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP).
 9    private String origin = "";
10    private final boolean async;
11 }

註意:一個Context生命期內Context只能初始化一次,因為是存到ThreadLocal中,並且只有在非null時才會進行初始化。

 

如果想在調用 SphU.entry() 或 SphO.entry() 前,自定義一個context,則通過ContextUtil.enter()方法來創建。context是保存在ThreadLocal中的,每次執行的時候會優先到ThreadLocal中獲取,為null時會調用 MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType())創建一個context。當Entry執行exit方法時,如果entry的parent節點為null,表示是當前Context中最外層的Entry了,此時將ThreadLocal中的context清空。

 

Entry

剛纔在Context身影中也看到了Entry的出現,現在就談談Entry。每次執行 SphU.entry() 或 SphO.entry() 都會返回一個Entry,Entry表示一次資源操作,內部會保存當前invocation信息。在一個Context生命周期中多次資源操作,也就是對應多個Entry,這些Entry形成parent/child結構保存在Entry實例中,entry類CtEntry結構如下:

 1 class CtEntry extends Entry {
 2    protected Entry parent = null;
 3    protected Entry child = null;
 4 
 5    protected ProcessorSlot<Object> chain;
 6    protected Context context;
 7 }
 8 public abstract class Entry implements AutoCloseable {
 9    private long createTime;
10    private Node curNode;
11    /**
12     * {@link Node} of the specific origin, Usually the origin is the Service Consumer.
13     */
14    private Node originNode;
15    private Throwable error; // 是否出現異常
16    protected ResourceWrapper resourceWrapper; // 資源信息
17 }

Entry實例代碼中出現了Node,這個又是什麼東東呢 :(,咱們接著往下看:

 

DefaultNode

Node(關於StatisticNode的討論放到下一小節)預設實現類DefaultNode,該類還有一個子類EntranceNode;context有一個entranceNode屬性,Entry中有一個curNode屬性。

  • EntranceNode:該類的創建是在初始化Context時完成的(ContextUtil.trueEnter方法),註意該類是針對Context維度的,也就是一個context有且僅有一個EntranceNode。

  • DefaultNode:該類的創建是在NodeSelectorSlot.entry完成的,當不存在context.name對應的DefaultNode時會新建(new DefaultNode(resourceWrapper, null),對應resouce)並保存到本地緩存(NodeSelectorSlot中private volatile Map<String, DefaultNode> map);獲取到context.name對應的DefaultNode後會將該DefaultNode設置到當前context的curEntry.curNode屬性,也就是說,在NodeSelectorSlot中是一個context有且僅有一個DefaultNode。

 

看到這裡,你是不是有疑問?為什麼一個context有且僅有一個DefaultNode,我們的resouece跑哪去了呢,其實,這裡的一個context有且僅有一個DefaultNode是在NodeSelectorSlot範圍內,NodeSelectorSlot是ProcessorSlotChain中的一環,獲取ProcessorSlotChain是根據Resource維度來的。總結為一句話就是:針對同一個Resource,多個context對應多個DefaultNode;針對不同Resource,(不管是否是同一個context)對應多個不同DefaultNode。這還沒看明白 : (,好吧,我不bb了,上圖吧:

 

DefaultNode結構如下:

 1 public class DefaultNode extends StatisticNode {
 2    private ResourceWrapper id;
 3    /**
 4     * The list of all child nodes.
 5     * 子節點集合
 6     */
 7    private volatile Set<Node> childList = new HashSet<>();
 8    /**
 9     * Associated cluster node.
10     */
11    private ClusterNode clusterNode;
12 }

一個Resouce只有一個clusterNode,多個defaultNode對應一個clusterNode,如果defaultNode.clusterNode為null,則在ClusterBuilderSlot.entry中會進行初始化。

 

同一個Resource,對應同一個ProcessorSlotChain,這塊處理邏輯在lookProcessChain方法中,如下:

 1 ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
 2    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
 3    if (chain == null) {
 4        synchronized (LOCK) {
 5            chain = chainMap.get(resourceWrapper);
 6            if (chain == null) {
 7                // Entry size limit.
 8                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
 9                    return null;
10               }
11 
12                chain = SlotChainProvider.newSlotChain();
13                Map<ResourceWrapper, ProcessorSlotChain> newMap = newHashMap<ResourceWrapper, ProcessorSlotChain>(
14                    chainMap.size() + 1);
15                newMap.putAll(chainMap);
16                newMap.put(resourceWrapper, chain);
17                chainMap = newMap;
18           }
19       }
20   }
21    return chain;
22 }

 

StatisticNode

StatisticNode中保存了資源的實時統計數據(基於滑動時間視窗機制),通過這些統計數據,sentinel才能進行限流、降級等一系列操作。StatisticNode屬性如下:

 1 public class StatisticNode implements Node {
 2    /**
 3     * 秒級的滑動時間視窗(時間視窗單位500ms)
 4     */
 5    private transient volatile Metric rollingCounterInSecond = newArrayMetric(SampleCountProperty.SAMPLE_COUNT,
 6        IntervalProperty.INTERVAL);
 7    /**
 8     * 分鐘級的滑動時間視窗(時間視窗單位1s)
 9     */
10    private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000,false);
11    /**
12     * The counter for thread count. 
13 * 線程個數用戶觸發線程數流控
14     */
15    private LongAdder curThreadNum = new LongAdder();
16 }
17 public class ArrayMetric implements Metric {
18    private final LeapArray<MetricBucket> data;
19 }
20 public class MetricBucket {
21 // 保存統計值
22    private final LongAdder[] counters;
23 // 最小rt
24    private volatile long minRt;
25 }

其中MetricBucket.counters數組大小為MetricEvent枚舉值的個數,每個枚舉對應一個統計項,比如PASS表示通過個數,限流可根據通過的個數和設置的限流規則配置count大小比較,得出是否觸發限流操作,所有枚舉值如下:

public enum MetricEvent {
   PASS, // Normal pass.
   BLOCK, // Normal block.
   EXCEPTION,
   SUCCESS,
   RT,
   OCCUPIED_PASS
}

 

Slot

slot是另一個sentinel中非常重要的概念,sentinel的工作流程就是圍繞著一個個插槽所組成的插槽鏈來展開的。需要註意的是每個插槽都有自己的職責,他們各司其職完好的配合,通過一定的編排順序,來達到最終的限流降級的目的。預設的各個插槽之間的順序是固定的,因為有的插槽需要依賴其他的插槽計算出來的結果才能進行工作。

 

但是這並不意味著我們只能按照框架的定義來,sentinel 通過 SlotChainBuilder 作為 SPI 介面,使得 Slot Chain 具備了擴展的能力。我們可以通過實現 SlotsChainBuilder 介面加入自定義的 slot 並自定義編排各個 slot 之間的順序,從而可以給 sentinel 添加自定義的功能。

 

那SlotChain是在哪創建的呢?是在 CtSph.lookProcessChain() 方法中創建的,並且該方法會根據當前請求的資源先去一個靜態的HashMap中獲取,如果獲取不到才會創建,創建後會保存到HashMap中。這就意味著,同一個資源會全局共用一個SlotChain。預設生成ProcessorSlotChain為:

 1 // DefaultSlotChainBuilder
 2 public ProcessorSlotChain build() {
 3    ProcessorSlotChain chain = new DefaultProcessorSlotChain();
 4    chain.addLast(new NodeSelectorSlot());
 5    chain.addLast(new ClusterBuilderSlot());
 6    chain.addLast(new LogSlot());
 7    chain.addLast(new StatisticSlot());
 8    chain.addLast(new SystemSlot());
 9    chain.addLast(new AuthoritySlot());
10    chain.addLast(new FlowSlot());
11    chain.addLast(new DegradeSlot());
12 
13    return chain;

到這裡本文結束了,謝謝小伙伴們的閱讀~ 在理解了這些核心概念之後,相信聰明的你回過頭再看sentinel源碼就不會覺得有很大難度了 : )

 

 往期精選 


覺得文章不錯,對你有所啟發和幫助,希望能轉發給更多的小伙伴。如果有問題,請關註下麵公眾號,發送問題給我,多謝。
歡迎小伙伴關註【TopCoder】閱讀更多精彩好文。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ...
  • 現在越來越多的項目就算是一個管理後端也偏向於使用前後端分離的部署方式去做,為了順應時代的潮流,一前後端分離就產生了跨域問題,所以許多同學把跨域和前後端分離項目聯繫在了一起,其實跨域產生的原因並不是前後端分離導致的,那我們一起來看一下,希望可以靠這一篇文章解答大家所有的跨域問題 一、跨域產生的條件 使 ...
  • 背景介紹 上篇介紹了利用Nginx反向代理實現負載均衡,本文詳細講述Nginx下的幾種負載均衡策略。 輪詢 輪詢,顧名思義,就是輪流請求,基於上篇文章的介紹,我們將負載均衡策略聚焦於 文件的 。 在瀏覽器中對 連續發出請求,根據nginx請求日誌可以看出web02與web03訪問的次數是相同的。 加 ...
  • 1、簡介 SOA(Service Oriented Architecture)“面向服務的架構”:他是一種設計方法,其中包含多個服務, 服務之間通過相互依賴最終提供一系列的功能。一個服務 通常以獨立的形式存在與操作系統進程中。各個服務之間 通過網路調用。 2、和微服務對比 微服務架構其實和 SOA ...
  • MTDDL 美團分散式數據訪問中間件(轉) "原文地址:MTDDL——美團點評分散式數據訪問層中間件" 因原文文字和圖顯示有問題,故整理於此,僅供參考。 業界方案 | 組件 | 簡介 | | : : | : | | Atlas | Qihoo 360開發維護的一個基於MySQL協議的數據中間層項目。 ...
  • 適配器主要用於介面的轉換或者將介面不相容的類對象組合在一起形成對外統一介面,是一種結構性模式,其本質是是一個中間件,適用於類及其對象。本文希望通過簡單的介紹和分析,能讓讀者對適配器模式有一個簡單直觀的認識和感知。 1.目的 對現有的類的介面進行轉換以符合新的需求。 2.動機 通過轉換或者組合,間接復 ...
  • CDN加速靜態文件訪問 全局調度 緩存技術 內容分發 帶寬優化 CDN是Content Delivery Network的縮寫,意思是內容分髮網絡。CDN的作用是把用戶需要的內容分發到離用戶近的地方,這樣可以使用戶 能夠就近獲取所需內容。 整個CDN系統(如圖1-1所示)分為CDN源站和CDN節點, ...
  • 3.3 如何從ubuntu或PC傳遞文件到板子,ubuntu如何上網? 答:以下將分別介紹如何在ubuntu和windows下如何傳遞文件。 ubuntu如何配置上網?ubuntu 上網:打開Oracle VM VirtualBox(虛擬機軟體,筆者以此軟體為例,也可用其他虛擬機軟體)管理器中的設置 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...