Sentinel熔斷降級


sentinel流量控制

Sentinel流量控制&服務熔斷降級介紹

流量控制介紹

image-20210303225557431

在這里我用景區的例子解釋一下

一個旅游景點日接待游客數量為8K,8K以后的游客就無法買票進去景區。

對應編程來說就是,一個接口QPS(每秒請求數)最大為100,在QPS100之后的請求我們就要限制其訪問,並給出友好提示。

不限制QPS無限的次數就會造成服務器的宕機。

服務熔斷降級

在調用系統的時候,如果調用鏈路中的某個資源出現了不穩定,最終會導致請求發生堆積,進而導致級聯錯誤

image-20210304102311130

而熔斷降級就可以解決這個問題,所謂的熔斷降級就是當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯故障

sentinel流量控制入門

創建本地應用

整體流程分析

  1. 創建springBoot項目
  2. 導入sentinel的坐標(此處使用為gradle)
  3. 創建Controller測試接口,定義限流規則同時引用限流規則

導入核心依賴

image-20210304103811187
implementation("com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel")

本地Sentinel控制台

下載sentinel控制台jar包

下載地址

啟動Sentinel控制台

啟動Sentinel控制台需要JDK版本為1.8及以上版本。

使用一下命令啟動

java -Dserver.port=端口號 -jar sentinel-dashboard-1.8.0.jar

訪問控制台

通過瀏覽器訪問http://localhost:端口號即可訪問控制台,默認用戶名密碼都是sentinel

image-20210304121702160

Sentinel自定義資源方式

  1. 拋出異常的方式定義資源
  2. 返回布爾值方式定義資源
  3. 異步調用支持
  4. 注解方式定義資源
  5. 主流框架的默認適配

拋出異常的方式定義資源

Sentinel種的SphU包含了try-catch風格的API。用這種方式,當資源發生了限流之后會拋出BlockException

這個時候就可以捕獲異常,進行限流之后的邏輯操作,在入門案例種就是使用此方式實現的,關鍵代碼:

fun hello(): String {
        //使用限流規則
        try {
            SphU.entry("Hello")
        } catch (e: Exception) {
            return "系統繁忙,請稍后再試!!"
        }
        return "Hello Sentinel"
    }

特別地,若 entry 的時候傳入了熱點參數,那么 exit 的時候也一定要帶上對應的參數(exit(count, args)),否則可能會有統計錯誤。這個時候不能使用 try-with-resources 的方式。另外通過 Tracer.trace(ex) 來統計異常信息時,由於 try-with-resources 語法中 catch 調用順序的問題,會導致無法正確統計異常數,因此統計異常信息時也不能在 try-with-resources 的 catch 塊中調用 Tracer.trace(ex)

手動 exit 示例:

Entry entry = null;
// 務必保證 finally 會被執行
try {
  // 資源名可使用任意有業務語義的字符串,注意數目不能太多(超過 1K),超出幾千請作為參數傳入而不要直接作為資源名
  // EntryType 代表流量類型(inbound/outbound),其中系統規則只對 IN 類型的埋點生效
  entry = SphU.entry("自定義資源名");
  // 被保護的業務邏輯
  // do something...
} catch (BlockException ex) {
  // 資源訪問阻止,被限流或被降級
  // 進行相應的處理操作
} catch (Exception ex) {
  // 若需要配置降級規則,需要通過這種方式記錄業務異常
  Tracer.traceEntry(ex, entry);
} finally {
  // 務必保證 exit,務必保證每個 entry 與 exit 配對
  if (entry != null) {
    entry.exit();
  }
}

熱點參數埋點示例:

Entry entry = null;
try {
    // 若需要配置例外項,則傳入的參數只支持基本類型。
    // EntryType 代表流量類型,其中系統規則只對 IN 類型的埋點生效
    // count 大多數情況都填 1,代表統計為一次調用。
    entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
    // Your logic here.
} catch (BlockException ex) {
    // Handle request rejection.
} finally {
    // 注意:exit 的時候也一定要帶上對應的參數,否則可能會有統計錯誤。
    if (entry != null) {
        entry.exit(1, paramA, paramB);
    }
}

SphU.entry() 的參數描述:

參數名 類型 解釋 默認值
entryType EntryType 資源調用的流量類型,是入口流量(EntryType.IN)還是出口流量(EntryType.OUT),注意系統規則只對 IN 生效 EntryType.OUT
count int 本次資源調用請求的 token 數目 1
args Object[] 傳入的參數,用於熱點參數限流

注意SphU.entry(xxx) 需要與 entry.exit() 方法成對出現,匹配調用,否則會導致調用鏈記錄異常,拋出 ErrorEntryFreeException 異常。常見的錯誤:

  • 自定義埋點只調用 SphU.entry(),沒有調用 entry.exit()
  • 順序錯誤,比如:entry1 -> entry2 -> exit1 -> exit2,應該為 entry1 -> entry2 -> exit2 -> exit1

返回布爾值方式定義資源

Sentinel種的SphO包含了if-else風格的API。用這種方式,當資源發生了限流之后會返回 false ,這時候可以根據返回值,進行限流之后的邏輯處理。

  1. 在項目中創建TestBooleanController中使用返回booelan的方式定義資源

     @GetMapping("sentinel_boolean")
        fun hello(): Boolean {
            //配置限流入口,在這里是使用控制台進行配置限流規則
            return if (SphO.entry("Boolean")) {
                try {//被保護的資源
                    println("Hello Sentinel Boolean")
                    true
                } finally {
                    //限流出口
                    SphO.exit()
                }
            } else {
                //限流或者降級處理
                println("系統繁忙,請稍后重試")
                false
            }
        }
    

**在控制台進行流控規則的配置 **

image-20210304153534536

注意 SphO.entry(xxx)需要於SphO.exit()方法成對出現,匹配調用,位置正確,否則就會導致調用鏈記錄異常,拋出ErrorEntryFreeException異常

異步調用支持

Sentinel 支持異步調用鏈路的統計。在異步調用中,需要通過 SphU.asyncEntry(xxx) 方法定義資源,並通常需要在異步的回調函數中調用 exit 方法。

  1. 啟動類開啟異步調用 @EnableAsync
  2. 開啟方法的異步調用 @Async

SphU.asyncEntry(xxx) 不會影響當前(調用線程)的 Context,因此以下兩個 entry 在調用鏈上是平級關系(處於同一層),而不是嵌套關系:

// 調用鏈類似於:
// -parent
// ---asyncResource
// ---syncResource
asyncEntry = SphU.asyncEntry(asyncResource);
entry = SphU.entry(normalResource);

若在異步回調中需要嵌套其它的資源調用(無論是 entry 還是 asyncEntry),只需要借助 Sentinel 提供的上下文切換功能,在對應的地方通過 ContextUtil.runOnContext(context, f) 進行 Context 變換,將對應資源調用處的 Context 切換為生成的異步 Context,即可維持正確的調用鏈路關系。示例如下:

public void handleResult(String result) {
    Entry entry = null;
    try {
        entry = SphU.entry("handleResultForAsync");
        // Handle your result here.
    } catch (BlockException ex) {
        // Blocked for the result handler.
    } finally {
        if (entry != null) {
            entry.exit();
        }
    }
}

public void someAsync() {
    try {
        AsyncEntry entry = SphU.asyncEntry(resourceName);

        // Asynchronous invocation.
        doAsync(userId, result -> {
            // 在異步回調中進行上下文變換,通過 AsyncEntry 的 getAsyncContext 方法獲取異步 Context
            ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
                try {
                    // 此處嵌套正常的資源調用.
                    handleResult(result);
                } finally {
                    entry.exit();
                }
            });
        });
    } catch (BlockException ex) {
        // Request blocked.
        // Handle the exception (e.g. retry or fallback).
    }
}

此時的調用鏈就類似於:

-parent
---asyncInvocation
-----handleResultForAsync

更詳細的示例可以參考 Demo 中的 AsyncEntryDemo,里面包含了普通資源與異步資源之間的各種嵌套示例。

注解方式定義資源

Sentinel 支持通過 @SentinelResource 注解定義資源並配置 blockHandlerfallback 函數來進行限流之后的處理。使用 Sentinel Annotation AspectJ Extension 的時候需要引入以下依賴

implementation("com.alibaba.csp:sentinel-annotation-aspectj:1.7.2")

@RestController
class TestAnnController {
    /**
     * SentinelResource: 定義支援
     * value:設置資源的名稱
     * blockHandler:設置降級或者限流的處理函數
     */
    @SentinelResource(value = "sentinel_ann", blockHandler = "exceptionHandler")
    @GetMapping("ann")
    fun hello(): String{
        return "hello Sentinel"
    }

    /**
     * 限流或者降級的函數
     */
    fun exceptionHandler(ex: BlockException) :String {
        ex.printStackTrace()
        return "系統繁忙,請稍后"
    }
}

@SentinelResource 注解

注意:注解方式埋點不支持 private 方法。

@SentinelResource 用於定義資源,並提供可選的異常處理和 fallback 配置項。 @SentinelResource 注解包含以下屬性:

  • value:資源名稱,必需項(不能為空)
  • entryType:entry 類型,可選項(默認為 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 對應處理 BlockException 的函數名稱,可選項。blockHandler 函數訪問范圍需要是 public,返回類型需要與原方法相匹配,參數類型需要和原方法相匹配並且最后加一個額外的參數,類型為 BlockException。blockHandler 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 blockHandlerClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
  • fallback/fallbackClass:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore 里面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:
    • 返回值類型必須與原函數返回值類型一致;
    • 方法參數列表需要和原函數一致,或者可以額外多一個 Throwable 類型的參數用於接收對應的異常。
    • fallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
  • defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,通常用於通用的 fallback 邏輯(即可以用於很多服務或方法)。默認 fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore 里面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。defaultFallback 函數簽名要求:
    • 返回值類型必須與原函數返回值類型一致;
    • 方法參數列表需要為空,或者可以額外多一個 Throwable 類型的參數用於接收對應的異常。
    • defaultFallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
  • exceptionsToIgnore(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。

1.8.0 版本開始,defaultFallback 支持在類級別進行配置。

注:1.6.0 之前的版本 fallback 函數只針對降級異常(DegradeException)進行處理,不能針對業務異常進行處理

特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandlerfallbackdefaultFallback,則被限流降級時會將 BlockException 直接拋出(若方法本身未定義 throws BlockException 則會被 JVM 包裝一層 UndeclaredThrowableException)。

示例:

public class TestService {

    // 原函數
    @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
    public String hello(long s) {
        return String.format("Hello at %d", s);
    }
    
    // Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數.
    public String helloFallback(long s) {
        return String.format("Halooooo %d", s);
    }

    // Block 異常處理函數,參數最后多一個 BlockException,其余與原函數一致.
    public String exceptionHandler(long s, BlockException ex) {
        // Do some log here.
        ex.printStackTrace();
        return "Oops, error occurred at " + s;
    }

    // 這里單獨演示 blockHandlerClass 的配置.
    // 對應的 `handleException` 函數需要位於 `ExceptionUtil` 類中,並且必須為 public static 函數.
    @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
    public void test() {
        System.out.println("Test");
    }
}

從 1.4.0 版本開始,注解方式定義資源支持自動統計業務異常,無需手動調用 Tracer.trace(ex) 來記錄業務異常。Sentinel 1.4.0 以前的版本需要自行調用 Tracer.trace(ex) 來記錄業務異常。

配置

Spring Cloud Alibaba

若您是通過 Spring Cloud Alibaba 接入的 Sentinel,則無需額外進行配置即可使用 @SentinelResource 注解。

Spring AOP

若您的應用使用了 Spring AOP(無論是 Spring Boot 還是傳統 Spring 應用),您需要通過配置的方式將 SentinelResourceAspect 注冊為一個 Spring Bean:

@Configuration
public class SentinelAspectConfiguration {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

我們提供了 Spring AOP 的示例,可以參見 sentinel-demo-annotation-spring-aop

AspectJ

若您的應用直接使用了 AspectJ,那么您需要在 aop.xml 文件中引入對應的 Aspect:

<aspects>
    <aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
</aspects>

主流框架的默認適配

image-20210304170247125

文檔地址:https://github.com/alibaba/Sentinel/wiki/主流框架的適配#spring-cloud

規則的種類

Sentinel 的所有規則都可以在內存態中動態地查詢及修改,修改之后立即生效。同時 Sentinel 也提供相關 API,供您來定制自己的規則策略。

Sentinel 支持以下幾種規則:流量控制規則熔斷降級規則系統保護規則來源訪問控制規則熱點參數規則

流量控制規則 (FlowRule)

流量規則的定義

重要屬性:

Field 說明 默認值
resource 資源名,資源名是限流規則的作用對象
count 限流閾值
grade 限流閾值類型,QPS 模式(1)或並發線程數模式(0) QPS 模式
limitApp 流控針對的調用來源 default,代表不區分調用來源
strategy 調用關系限流策略:直接、鏈路、關聯 根據資源本身(直接)
controlBehavior 流控效果(直接拒絕/WarmUp/勻速+排隊等待),不支持按調用關系限流 直接拒絕
clusterMode 是否集群限流

同一個資源可以同時有多個限流規則,檢查規則時會依次檢查。

通過代碼定義流量控制規則

理解上面規則的定義之后,我們可以通過調用 FlowRuleManager.loadRules() 方法來用硬編碼的方式定義流量控制規則,比如:

private void initFlowQpsRule() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule(resourceName);
    // set limit qps to 20
    rule.setCount(20);
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setLimitApp("default");
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM