Sentinel 實戰-限流篇

逅弈逐碼發表於2019-01-09

逅弈 轉載請註明原創出處,謝謝!

系列文章

Sentinel 原理-全解析
Sentinel 原理-呼叫鏈
Sentinel 原理-滑動視窗
Sentinel 原理-實體類
Sentinel 實戰-控制檯篇
Sentinel 實戰-規則持久化
Sentinel 實戰-叢集限流篇

Sentinel 系列教程,現已上傳到 github 和 gitee 中:

5417792-a2e73a7ca4dda700.png
sentinel-tutorial.png

我們已經知道了 Sentinel 的三大功能:限流 降級 系統保護。現在讓我們來了解下具體的使用方法,以限流來演示具體的步驟。

引入依賴

首先肯定是要先引入需要的依賴,如下所示:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>x.y.z</version>
</dependency>

這裡的版本號 x.y.z 可以根據需要自行選擇,我選擇的是截至目前為止的最新版:1.4.0。

定義資源

假設我們有一個 UserService :

public class UserService {
    /**
     * 根據uid獲取使用者資訊
     * @param uid uid
     * @return 使用者資訊
     */
    public User getUser(Long uid){
        // 業務程式碼
        User user = new User();
        user.setUid(uid);
        user.setName("user-" + uid);
        return user;
    }

    public static class User {
        private Long uid;
        private String name;
        // 省略getter、setter
    }
}

現在我們要對 getUser 方法進行限流,那首先我們要定義一個資源,在 sentinel 中資源是抽象出來做具體的操作的,用資源來保護我們的程式碼和服務。

使用者只需要為受保護的程式碼或服務定義一個資源,然後定義規則就可以了,剩下的都交給sentinel來處理了。定義完資源後,就可以通過在程式中埋點來保護你自己的服務了,埋點的方式有兩種:丟擲異常和返回布林值。

下面我用丟擲異常的方式進行埋點:

// 定義的資源
public static final String USER_RES = "userResource";

public User getUser(Long uid){
    Entry entry = null;
    try {
        // 流控程式碼
        entry = SphU.entry(USER_RES);
        // 業務程式碼
        User user = new User();
        user.setUid(uid);
        user.setName("user-" + uid);
        return user;
    }catch(BlockException e){
        // 被限流了
        System.out.println("[getUser] has been protected! Time="+System.currentTimeMillis());
    }finally {
        if(entry!=null){
            entry.exit();
        }
    }
    return null;
}

除了通過跑出異常的方式定義資源外,返回布林值的方式也是一樣的,這裡不具體展開了。

PS:如果你不想對原有的業務程式碼進行侵入,也可以通過註解 SentinelResource 來進行資源埋點。

定義規則

定義完資源後,就可以來定義限流的規則了,但是我們需要對流控規則做個詳細的瞭解,以便更好的進行限流的操作,流控的規則對應的是 FlowRule。

一條FlowRule有以下幾個重要的屬性組成:

  • resource: 規則的資源名
  • grade: 限流閾值型別,qps 或執行緒數
  • count: 限流的閾值
  • limitApp: 被限制的應用,授權時候為逗號分隔的應用集合,限流時為單個應用
  • strategy: 基於呼叫關係的流量控制
  • controlBehavior:流控策略

前三個屬性比較好理解,最後三個比較難理解,讓我們來詳細看下最後三個屬性:

limitApp

首先讓我們來看下limitApp,從字面上看是指要限制哪個應用的意思,主要是用於根據呼叫方進行流量控制。

他有三種情況可以選擇:

  • default

表示不區分呼叫者,來自任何呼叫者的請求都將進行限流統計。

  • {some_origin_name}

表示針對特定的呼叫者,只有來自這個呼叫者的請求才會進行流量控制。

例如:資源 NodeA 配置了一條針對呼叫者 caller1 的規則,那麼當且僅當來自 caller1NodeA 的請求才會觸發流量控制。

  • other

表示除 {some_origin_name} 以外的其餘呼叫方的流量進行流量控制。

例如:資源 NodeA 配置了一條針對呼叫者 caller1 的限流規則,同時又配置了一條呼叫者為 other 的規則,那麼任意來自非 caller1NodeA 的呼叫,都不能超過 other 這條規則定義的閾值。

strategy

基於呼叫關係的流量控制,也有三種情況可以選擇:

  • STRATEGY_DIRECT

根據呼叫方進行限流。ContextUtil.enter(resourceName, origin) 方法中的 origin 引數標明瞭呼叫方的身份。

如果 strategy 選擇了DIRECT ,則還需要根據限流規則中的 limitApp 欄位根據呼叫方在不同的場景中進行流量控制,包括有:”所有呼叫方“、”特定呼叫方origin“、”除特定呼叫方origin之外的呼叫方“。

  • STRATEGY_RELATE

根據關聯流量限流。當兩個資源之間具有資源爭搶或者依賴關係的時候,這兩個資源便具有了關聯,可使用關聯限流來避免具有關聯關係的資源之間過度的爭搶。

比如對資料庫同一個欄位的讀操作和寫操作存在爭搶,讀的速度過高會影響寫得速度,寫的速度過高會影響讀的速度。

舉例來說:read_db 和 write_db 這兩個資源分別代表資料庫讀寫,我們可以給 read_db 設定限流規則來達到寫優先的目的:設定 FlowRule.strategy 為 RuleConstant.STRATEGY_RELATE,同時設定 FlowRule.refResource 為 write_db。這樣當寫庫操作過於頻繁時,讀資料的請求會被限流。

  • STRATEGY_CHAIN

根據呼叫鏈路入口限流。假設來自入口 Entrance1 和 Entrance2 的請求都呼叫到了資源 NodeA,Sentinel 允許根據某個入口的統計資訊對資源進行限流。

舉例來說:我們可以設定 FlowRule.strategy 為 RuleConstant.CHAIN,同時設定 FlowRule.refResource 為 Entrance1 來表示只有從入口 Entrance1 的呼叫才會記錄到 NodeA 的限流統計當中,而對來自 Entrance2 的呼叫可以放行。

controlBehavior

流控策略,主要是發生攔截後具體的流量整形和控制策略,目前有三種策略,分別是:

  • CONTROL_BEHAVIOR_DEFAULT

這種方式是:直接拒絕,該方式是預設的流量控制方式,當 qps 超過任意規則的閾值後,新的請求就會被立即拒絕,拒絕方式為丟擲FlowException。

這種方式適用於對系統處理能力確切已知的情況下,比如通過壓測確定了系統的準確水位。

  • CONTROL_BEHAVIOR_WARM_UP

這種方式是:排隊等待 ,又稱為 冷啟動。該方式主要用於當系統長期處於低水位的情況下,流量突然增加時,直接把系統拉昇到高水位可能瞬間把系統壓垮。

通過"冷啟動",讓通過的流量緩慢增加,在一定時間內逐漸增加到閾值上限,給冷系統一個預熱的時間,避免冷系統被壓垮的情況。

  • CONTROL_BEHAVIOR_RATE_LIMITER

這種方式是:慢啟動,又稱為 勻速器模式。這種方式嚴格控制了請求通過的間隔時間,也即是讓請求以均勻的速度通過,對應的是漏桶演算法。

這種方式主要用於處理間隔性突發的流量,例如訊息佇列。想象一下這樣的場景,在某一秒有大量的請求到來,而接下來的幾秒則處於空閒狀態,我們希望系統能夠在接下來的空閒期間逐漸處理這些請求,而不是在第一秒直接拒絕多餘的請求。

具體的 FlowRule 可以用下面這張圖表示:

5417792-40ccbb65cd5384d7.png
flow-rule-factors.png

規則定義好了之後,啟動應用後,就會自動對我們的業務程式碼進行保護了,當然實際生產環境中不可能通過硬編碼的方式來定義規則的,sentinel 為我們提供了 DataSource 介面,通過實現該介面可以自定義規則的儲存資料來源。

通過 DataSource 介面可以有很多種方式對規則進行持久化,例如:

  • 整合動態配置系統,如 ZooKeeper、Nacos 等,動態地實時重新整理配置規則
  • 結合 RDBMS、NoSQL、VCS 等來實現該規則
  • 配合 Sentinel Dashboard 使用

本篇文章不對規則的持久化做具體的介紹,本篇文章主要是實現一個簡單的限流的例子的接入。

PS:DateSource 介面在後期已經被拆成 ReadableDataSource 和 WritableDataSource 介面了。

檢視日誌

我們通過一個Spring Boot專案來啟動,其中 UserService 作為一個專案中一個具體的服務,專案啟動好之後,會在 ${userhome}/logs/csp/ 目錄下建立一個 sentinel-record.log.${date} 的日誌檔案,該檔案會記錄 sentinel 的重要的行為,我本地的日誌檔案如下所示:

5417792-3e72581373dd39ea.png
sentinel-logs-1.png

從上圖中可以看到,除了 sentinel-record 之外,還有 sentinel-exception 檔案,從命名上就可以知道這個檔案是記錄 sentinel 執行過程中出現的異常的。

讓我們開啟 sentinel-record.log.2019-01-02.0 的檔案看下具體的內容:

5417792-be5ea8b7ca57e874.png
sentinel-logs-2.png

sentinel-record 日誌中會記錄載入好的規則等資訊,具體的實時統計日誌會在另一個 叫 xx-metrics.log.${date} 的檔案中。

檢視統計效果

首先我們要訪問一下我們的服務,觸發了 sentinel 的限流規則後,才會生成具體的統計檔案。

5417792-d8fd3ac9587af508.png
call-resource.png

可以發現該方法成功返回了,現在讓我們來看看 ~/logs/csp/ 目錄下生成的統計檔案,如下圖所示:

5417792-4460d2680decf9c2.png
sentinel-logs-3.png

該統計檔案的命名方式是 ${appName}-metrics.log.${date} ,其中 ${appName} 會優先獲取的系統引數 project.name 的值,如果獲取不到會從啟動引數中獲取,具體的獲取方式在 AppNameUtil 類中。

我們開啟 lememo-retircs 檔案,看到如下的資訊:

5417792-feb6d82f8a87bc12.png
sentinel-logs-4.png

可以看到我們請求了很多次該資源後,sentinel 把每秒的統計資訊都列印出來了,用 | 來分隔不同的引數,一共有8個引數,從左至右分別是:

5417792-305c873d1dd41de9.png
sentinel-metrics-param.png

可以看到我們的請求都已經成功通過了,現在我們把規則中設定的 count 閾值改為1,然後重啟服務後,再次請求該服務,然後再次開啟 lememo-metrics 檔案,如下圖所示:

5417792-ceb7a928dca385ce.png
sentinel-logs-5.png

可以看到每秒中只有1個請求通過了,其他的都被 block 了,再看我們在程式碼中列印的日誌:

5417792-42abb556074b12bc.png
sentinel-logs-6.png

本篇文章通過一個例子對 sentinel 的限流進行了實戰,瞭解了規則的詳細作用,也知道通過 sentinel 列印的日誌來檢視執行過程中狀態。

但是這種方式比較原始,不管是建立規則,還是檢視日誌,下篇文章我將通過 sentinel 自帶的控制檯帶大家瞭解具體的作用。

5417792-f0b1dcdbbfebcbad.jpg
更多原創好文,請關注「逅弈逐碼」

相關文章