阿里P7大佬帶你解密Sentinel

概述

在接連寫了兩篇關於限流的文章(《面試補習》- 你來說說什麼是限流?, 限流神器Sentinel,不了解一下嗎?)後,總感覺還差最後一點內容來閉環整個限流相關的內容,這兩天在翻查相關文章後,找到了一篇通俗易懂的 《Sentinel 解密》 文章,感謝 寒舟大佬提供的幫助~ 將這部分內容補全。

1、Sentinel 概念

1.1、程式碼結構

image.png

1.2、核心概念

Resource

資源是 Sentinel 對所保護的內容的抽象,任何想保護的程式碼函數等都可以通過 SphU.entry 介面將其定義為一個資源,SphU.entry 介面的第一個參數描述了該資源的名稱

Node

Node是sentinel中用來存儲統計數據的記憶體結構,以樹形結構和hash存儲:

image.png

一個resource按照不同入口設置不同統計結點,存儲context級別的統計資訊;一個resource下設置統一的cluster node,存儲resource粒度的統計資訊;每個cluster node根據來源下掛不同origin node,存儲各個來源的統計資訊。

配置中不同限流模式其實最終對應的就是選擇不同的node進行計算:

  • 直接模式: 選擇cluster node
  • 關聯模式: 選擇關聯resource的cluster node
  • 應用淶源: 選擇origin node
  • 鏈路模式: 選擇default node

Node 類型:

  • StatisticNode:最為基礎的統計節點,是後續三種節點的父類,包含秒級和分鐘級兩個滑動窗口結構。
  • DefaultNode:鏈路節點,用於統計調用鏈路上某個資源的數據,維持樹狀結構。
  • ClusterNode:簇點,用於統計每個資源全局的數據(不區分調用鏈路),以及存放該資源的按來源區分的調用數據
  • EntranceNode:入口節點,特殊的鏈路節點,對應某個 Context 入口的所有調用數據。

image.png

Context

Context是對資源操作時的上下文環境, 同一個執行緒中通過thread local 進行傳里, context中維護了currentEntry, 同一個執行緒中可以多次entry/exit, 所以entry同樣維護了一個樹狀結構:

image.png

Entry

調用點,這裡面保存著和某一特定資源的某一次調用相關的資訊,比如這次調用的起始執行時間,結束時間,是否拋出異常,調用鏈路中的父子關係,該調用點對應資源的處理鏈(ProcessorSlotChain),還保存了該調用點所對應的資源統計節點

Slot

slot是規則處理器,通過責任鏈模式進行串聯處理。

image.png

1.3、核心類圖

image.png

sentinel主要通過Node實現結點維護統計資訊、通過LeapArray實現滑動窗口演算法。

2、Sentinel 流程

2.1、規則配置

image.png

應用通過訂閱監聽模式實現各種監聽器,監聽然後對應不同配置的進行更新,圖中列出了幾個主要監聽器,如: 規則配置、基本配置、集群配置等。

2.2、規則驗證

image.png

應用調用entry入口即啟動了規則驗證流程,主要步驟為獲取context, 構建並執行slot chain,如果違反規則紀錄失敗計數,清理contxt;反之,則紀錄成功計數,返回驗證成功。

3、Sentinel 核心

3.1、限流演算法

我們所熟知的限流演算法包括: 1. 計數器 2. 固定窗口 3. 滑動窗口 4.漏桶 5.令牌桶等。

  • 1、計數器,維護一個計數器,訪問進入計數器+1,如果超過闕值則拒絕、訪問結束計數器-1。
  • 2、固定窗口,給定一個時間窗口,維護一個計數器、如果窗口內訪問次數大於闕值則拒絕。
  • 3、滑動窗口,固定窗口有臨界問題,所以提出滑動窗口思想,將一個大的時間窗口切分成更細粒度的子窗口,每個子窗口單獨統計,每過一個子窗口的時間,就向右滑動一個子窗口。
  • 4、漏桶,滑動窗口不能解決平滑度問題,為了解決平滑度問題,提出漏桶演算法進行流速控制。
  • 5、令牌桶: 跟漏桶演算法相反,以很定的速度產生令牌,桶滿則丟棄,請求訪問時從桶里後去令牌,如果能夠獲取,則通過否則拒絕。

Sentinel中主要使用了計數器滑動窗口漏桶限流思想。比如對於執行緒數的流控,就是直接使用計數器進行限制,對於QPS的限制則使用了滑動窗口的思想,滑動窗口也是Sentinel的主要流控實現方式。

3.2、滑動窗口

image.png

Sentinel支援秒級的窗口,默認窗口大小1秒,假設分為5個子窗口,則每個子窗口的大小為200ms, 如上圖,每個格子有個索引timeId, 那窗口是如何進行滑動呢?演算法的關鍵實現就是在前面類圖提到過的currentWindow()方法:

  • 1、根據當前時間戳計算timeId
  • 2、根據timdId獲取當前子窗口
  • 3、如果子窗口不存在,則創建子窗口
  • 4、如果子窗口已經存在,則根據當前時間計算開始開始時間,對比子窗口的開始時間,如果開始時間一致,則說明此子窗口即當前窗口,如果大於子窗口時間,則需要滑動
  • 5、滑動其實就是resetWidow, 包括reset windowStartTime, window中的統計資訊等,如上圖所示

3.3、優先順序

image.png

優先順序的目的是為了區分正常流量和其他低優先順序流量,比如壓測流量。對於正常流量,如果超過闕值,不想直接拒絕,嘗試借用將來窗口的指標,如果借用成功,則請求通過,如果借用失敗,則拒絕。當時間窗口真正走到將來的時間點時,需要將借用窗口的計數器恢復設置。

那如何當前請求是否可以借用呢?如下圖,主要維護一個借用時間闕值,計算一秒窗口內的總數有沒有超過QPS,如果沒有超過看借用時間是否超過借用闕值:

image.png

3.4、熱點參數

一般資源的定義需要可枚舉的,不能爆炸式的,假如對每個userId進行限流,這樣會導致sentinel的存儲空間爆炸式增長,最後導致OOM. 對於這種細粒度的參數限流,sentinel提供了熱點參數方案。

如圖:

熱點參數就是在正常統計資訊而外增加了參數統計,一樣支援執行緒數和QPS兩種限流方式,對於執行緒數,每個參數索引維護了一個LRU cache; 對於QPS,每個參數索引、每個子窗口、每個統計緯度維護了一個LRU cache, cache中維護了各種計數。通過LRU cache可以避免記憶體爆炸, 達到熱點參數限流的目的。

image.png

點關注,不迷路

好了各位,以上就是這篇文章的全部內容了,我後面會每周都更新幾篇高品質的大廠面試和常用技術棧相關的文章。感謝大夥能看到這裡,如果這個文章寫得還不錯, 求三連!!! 創作不易,感謝各位的支援和認可,我們下篇文章見!

我是 九靈 ,有需要交流的童鞋可以 加我wx,Jayce-K,關注公眾號:Java 補習課,掌握第一手資料!
如果本篇部落格有任何錯誤,請批評指教,不勝感激 !