SpringCloudAlibaba分散式流量控制元件Sentinel實戰與原始碼分析(上)

itxiaoshen發表於2022-06-10

概述

定義

Sentinel官網地址 https://sentinelguard.io/zh-cn/index.html 最新版本v1.8.4

Sentinel官網文件地址 https://sentinelguard.io/zh-cn/docs/introduction.html

Sentinel GitHub原始碼地址 https://www.github.com/alibaba/Sentinel

Sentinel是面向分散式服務架構的高可用防護元件,主要以流量為切入點,從流量控制、熔斷降級、系統自適應保護等多個維度來幫助使用者保障微服務的穩定性。

背景

分散式系統遇到常見棘手問題就是如何保障系統高可用性的場景,嚴重則會導致整個系統服務雪崩。

image-20220605121156566

在微服務架構中,出現服務交叉呼叫是很常見的情況,如電商秒殺商品、商品詳情、購物車等都會訪問庫存服務。如果其中下單服務不可用,就會出現執行緒池裡的所有執行緒都因等待而被阻塞,進而導致整個系統的服務雪崩。服務雪崩效應是因服務提供者的不可用導致服務呼叫者的不可用,並將不可用逐步放大的過程。特別如果被一些非核心業務如積分服務而導致整個服務不可用那就非常不值得,假如積分服務有一些容錯處理機制,到時在事後加一些補償的機制加回來也是可以接收的。

image-20220605121619870

常見容錯機制

  • 超時機制:一旦出現超時就釋放資源,釋放資源的速度相對還是較快的,一定程度上可以抑制資源耗盡的問題。
  • 服務限流:如QPS超時一定值如1000直接拒絕。
  • 隔離:隔離可以根據執行緒數量來限制,也可以根據訊號來限制。執行緒隔離比如可以為每一服務限制可訪問的執行緒數量。訊號隔離可以限制併發訪問,和限流有點類似。
  • 服務熔斷:遠端服務不可用或網路抖動則暫時關閉服務。其作用如保險絲。
  • 服務降級:當出現服務熔斷後,服務將不再被呼叫,可以準備一個本地fallback回撥,返回一些本地預設處理,作為一種補償的機制。

特性

  • 豐富的應用場景:阿里巴巴 10 年雙十一積累的豐富流量場景,包括秒殺、雙十一零點持續洪峰、熱點商品探測、預熱、訊息佇列削峰填谷等多樣化的場景。
  • 易於使用,快速接入:簡單易用,開源生態廣泛,針對 Dubbo、Spring Cloud、gRPC、Zuul、Reactor、Quarkus 等框架只需要引入適配模組即可快速接入。
  • 多樣化的流量控制:資源粒度、呼叫關係、指標型別、控制效果等多維度的流量控制和降級能力。
  • 視覺化的監控和規則管理:簡單易用的 Sentinel 控制檯,秒級的實時監控和動態規則管理。

image-20220605124452211

基本概念

  • 資源
    • 資源是 Sentinel 的關鍵概念。它可以是 Java 應用程式中的任何內容,例如,由應用程式提供的服務,或由應用程式呼叫的其它應用提供的服務,甚至可以是一段程式碼。在接下來的文件中,我們都會用資源來描述程式碼塊。
    • 只要通過 Sentinel API 定義的程式碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作為資源名來標示資源。
  • 規則
    • 圍繞資源的實時狀態設定的規則,可以包括流量控制規則、熔斷降級規則以及系統保護規則。所有規則可以動態實時調整。

功能和設計理念

  • 流量控制

    • 流量控制在網路傳輸中是一個常用的概念,它用於調整網路包的傳送資料。然而,從系統穩定性角度考慮,在處理請求的速度上,也有非常多的講究。任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制。Sentinel 作為一個調配器,可以根據需要把隨機的請求調整成合適的形狀,如下圖所示:

    image-20220604124239025

    Sentinel 的設計理念可以自由選擇控制的角度,並進行靈活組合,從而達到想要的效果流量控制。流量控制有以下幾個角度:

    • 資源的呼叫關係,例如資源的呼叫鏈路,資源和資源之間的關係;
    • 執行指標,例如 QPS、執行緒池、系統負載等;
    • 控制的效果,例如直接限流、冷啟動、排隊等。
  • 熔斷降級

    • 降低呼叫鏈路中的不穩定資源也是 Sentinel 的使命之一。由於呼叫關係的複雜性,如果呼叫鏈路中的某個資源出現了不穩定,最終會導致請求發生堆積。Sentinel 和 Hystrix 的原則是一致的: 當呼叫鏈路中某個資源出現不穩定,例如,表現為 timeout,異常比例升高的時候,則對這個資源的呼叫進行限制,並讓請求快速失敗,避免影響到其它的資源,最終產生雪崩的效果。

    • 設計理念:在限制的手段上,Sentinel 和 Hystrix 採取了完全不一樣的方法。Hystrix 通過執行緒池的方式,來對依賴(在我們的概念中對應資源)進行了隔離。這樣做的好處是資源和資源之間做到了最徹底的隔離。缺點是除了增加了執行緒切換的成本,還需要預先給各個資源做執行緒池大小的分配。Sentinel 對這個問題採取了兩種手段:

      • 通過併發執行緒數進行限制

      和資源池隔離的方法不同,Sentinel 通過限制資源併發執行緒的數量,來減少不穩定資源對其它資源的影響。這樣不但沒有執行緒切換的損耗,也不需要您預先分配執行緒池的大小。當某個資源出現不穩定的情況下,例如響應時間變長,對資源的直接影響就是會造成執行緒數的逐步堆積。當執行緒數在特定資源上堆積到一定的數量之後,對該資源的新請求就會被拒絕。堆積的執行緒完成任務後才開始繼續接收請求。

      • 通過響應時間對資源進行降級

      除了對併發執行緒數進行控制以外,Sentinel 還可以通過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長後,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間視窗之後才重新恢復。

  • 系統負載保護

    • Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防護中重要的一環。當系統負載較高的時候,如果還持續讓請求進入,可能會導致系統崩潰,無法響應。在叢集環境下,網路負載均衡會把本應這臺機器承載的流量轉發到其它的機器上去。如果這個時候其它的機器也處在一個邊緣狀態的時候,這個增加的流量就會導致這臺機器也崩潰,最後導致整個叢集不可用。
    • 針對這個情況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力範圍之內處理最多的請求。

同類元件功能對比Sentinel VS Hystrix VS resilience4j

image-20220605231820322

主要工作機制

  • 對主流框架提供適配或者顯示的 API,來定義需要保護的資源,並提供設施對資源進行實時統計和呼叫鏈路分析。
  • 根據預設的規則,結合對資源的實時統計資訊,對流量進行控制。同時,Sentinel 提供開放的介面,方便定義及改變規則。
  • Sentinel 提供實時的監控系統,方便快速瞭解目前系統的狀態。

工作主流程

在 Sentinel 裡面,所有的資源都對應一個資源名稱以及一個 Entry。Entry 可以通過對主流框架的適配自動建立,也可以通過註解的方式或呼叫 API 顯式建立;每一個 Entry 建立的時候,同時也會建立一系列功能插槽(slot chain)。這些插槽有不同的職責,例如:

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

總體框架如下:

image-20220605194255184

Sentinel 將 ProcessorSlot 作為 SPI 介面進行擴充套件(1.7.2 版本以前 SlotChainBuilder 作為 SPI),使得 Slot Chain 具備了擴充套件的能力。您可以自行加入自定義的 slot 並編排 slot 間的順序,從而可以給 Sentinel 新增自定義的功能。

開源框架適配和多語言支援

為了減少開發的複雜程度,對大部分的主流框架例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了適配。只需要引入對應的依賴即可方便地整合 Sentinel。

image-20220605195114815

Sentinel 目前提供 Java、Go、C++ 、RUST等語言的原生支援。

實戰

組成部分

Sentinel 可以簡單的分為 Sentinel 核心庫和 Dashboard,核心庫不依賴 Dashboard,但是結合 Dashboard 可以取得最好的效果。

  • 核心庫(Java 客戶端):不依賴任何框架/庫,能夠執行於 Java 8 及以上的版本的執行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支援(見 主流框架適配)。
  • 控制檯(Dashboard):Dashboard 主要負責管理推送規則、監控、管理機器資訊等。

三步構建

我們說的資源,可以是任何東西,服務,服務裡的方法,甚至是一段程式碼。使用 Sentinel 來進行資源保護,主要分為幾個步驟:

  • 定義資源:資源 是 Sentinel 中的核心概念之一。最常用的資源是我們程式碼中的 Java 方法。 當然也可以更靈活的定義你的資源,例如,把需要控制流量的程式碼用 Sentinel API SphU.entry("HelloWorld")entry.exit() 包圍起來即可。
  • 定義規則:通過流控規則來指定允許該資源通過的請求次數
  • 檢驗規則是否生效

使用之前得先引入依賴

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

入門初體驗

package cn.itxs.ecom.storage.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/sentinel")
public class SentinelHelloControoler {

    private static final String RESOURCE_NAME = "hello";

    @RequestMapping("/hello")
    public String hello(){
        // 1.5.0 版本開始可以利用 try-with-resources 特性(使用有限制)
        // 資源名可使用任意有業務語義的字串,比如方法名、介面名或其它可唯一標識的字串。
        try (Entry entry = SphU.entry(RESOURCE_NAME)) {
            // 被保護的業務邏輯
            return "hello itxs";
        } catch (BlockException ex) {
            // 資源訪問阻止,被限流或被降級
            // 在此處進行相應的處理操作
            return "hello itxs block!";
        }
    }

    @PostConstruct
    private void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource(RESOURCE_NAME);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // Set limit QPS to 2.
        rule.setCount(2);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

連續訪問訪問測試http://localhost:4081/sentinel/hello,可以看到被流控了。

image-20220607234322885

@SentinelResource使用

使用提供的@SentinelResource註解,getAccount() 方法就成了一個資源。注意註解支援模組需要配合 Spring AOP 或者 AspectJ 一起使用。

    @RequestMapping("/account")
    @SentinelResource(value = ACCOUNT_RESOURCE_NAME,blockHandler = "blockHandlerForGetAccount")
    public Account getAccount(Integer id){
        return new Account(1,"1001",100);
    }

    public Account blockHandlerForGetAccount(Integer id,BlockException ex){
        ex.printStackTrace();
        return new Account(1,"被流控了",100);
    }

image-20220608000156131

連續訪問測試http://localhost:4081/sentinel/account,可以看到也被流控了。

image-20220608000113564

異常處理fallback機制

    @RequestMapping("/account_exception")
    @SentinelResource(value = ACCOUNT_RESOURCE_NAME,fallback = "fallbackHandlerForGetAccount")
    public Account getAccountException(Integer id){
        int i = 1/0;
        return new Account(1,"1001",100);
    }

    public Account fallbackHandlerForGetAccount(Integer id,Throwable ex){
        ex.printStackTrace();
        return new Account(1,"異常處理",100);
    }

image-20220608001640055

訪問測試http://localhost:4081/sentinel/account_exception,可以看到走fallback方法處理了。

image-20220608001526306

熔斷降級

    @PostConstruct
    private void initDegradeRules(){
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(DEGRADE_RESOURCE_NAME);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        rule.setCount(2);
        rule.setMinRequestAmount(2);
        rule.setStatIntervalMs(60*1000);
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

    @RequestMapping("/degrade")
    @SentinelResource(value = DEGRADE_RESOURCE_NAME,entryType = EntryType.IN,blockHandler = "blockHandlerForDegrade")
    public Account degrade(Integer id){
        throw new RuntimeException("異常");
    }

    public Account blockHandlerForDegrade(Integer id,BlockException ex){
        ex.printStackTrace();
        return new Account(1,"熔斷降級",100);
    }

連續訪問兩次訪問異常http://localhost:4081/sentinel/account_exception,之後十秒內訪問都直接熔斷降級,10秒後再訪問兩次顯示異常

image-20220608235225659

Sentinel 控制檯

功能

Sentinel 提供一個輕量級的開源控制檯,它提供機器發現以及健康情況管理、監控(單機和叢集),規則管理和推送的功能。通過整合 Sentinel 核心庫和 Dashboard。Sentinel 控制檯包含如下功能:

  • 檢視機器列表以及健康情況:收集 Sentinel 客戶端傳送的心跳包,用於判斷機器是否線上。
  • 監控 (單機和叢集聚合):通過 Sentinel 客戶端暴露的監控 API,定期拉取並且聚合應用監控資訊,最終可以實現秒級的實時監控。
  • 規則管理和推送:統一管理推送規則。
  • 鑑權:生產環境中鑑權非常重要。這裡每個開發者需要根據自己的實際情況進行定製。

注意:Sentinel 控制檯目前僅支援單機部署。Sentinel 控制檯專案提供 Sentinel 功能全集示例,不作為開箱即用的生產環境控制檯,若希望在生產環境使用請根據文件自行進行定製和改造。

docker部署

# 下載 docker pull bladex/sentinel-dashboard:tagname,沒有tag預設下載最新版本latest,本次使用1.8.4版本bladex/sentinel-dashboard:1.8.4
docker pull bladex/sentinel-dashboard
# 執行,訪問http 8858埠進入dashboard,使用者名稱和密碼sentinel/sentinel
docker run --name sentinel  -d -p 8858:8858 -d  bladex/sentinel-dashboard

二進位制包部署

可以從其GitHub專案的 release 頁面 下載最新版本的控制檯 jar 包。目前最新版本為v1.8.4,也可以從最新版本的原始碼自行構建 Sentinel 控制檯的jar包。由於下載原始碼1.8.4版本,那這裡我們執行編譯控制檯原始碼得到二進位制包。

image-20220610000842516

# 啟動 Sentinel 控制檯需要 JDK 版本為 1.8 及以上版本。其中 -Dserver.port=8858 用於指定 Sentinel 控制檯埠為 8858,沒有指定為8080。從 Sentinel 1.6.0 起,Sentinel 控制檯引入基本的登入功能,預設使用者名稱和密碼都是 sentinel
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=itxs -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard.jar

如果程式是Spring Boot 或 Spring Cloud 應用,則可以通過 Spring 配置檔案來指定配置。

image-20220610001144479

訪問http://localhost:8858/

image-20220610001237572

輸入上面啟動命令中的使用者密碼,進入控制檯主頁面

image-20220610001331388

客戶端接入控制檯

控制檯啟動後,客戶端需要按照以下步驟接入到控制檯。

  • 引入JAR包:客戶端需要引入 Transport 模組來與 Sentinel 控制檯進行通訊。在 pom.xml 引入 JAR 包:
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.4</version>
</dependency>
  • 配置啟動引數:啟動時加入 JVM 引數 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制檯地址和埠。若啟動多個應用,則需要通過 -Dcsp.sentinel.api.port=xxxx 指定客戶端監控 API 的埠(預設是 8719)
  • 觸發客戶端初始化:確保客戶端有訪問量,Sentinel 會在客戶端首次呼叫的時候進行初始化,開始向控制檯傳送心跳包。注意:還需要根據應用型別和接入方式引入對應的 適配依賴,否則即使有訪問量也不能被 Sentinel 統計。

如果是整合SpringCloud Alibaba加入

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2021.0.1.0</version>
        </dependency>

**本人部落格網站 **IT小神 www.itxiaoshen.com

相關文章