阿里限流神器Sentinel奪命連環 17 問?

bucaichenmou發表於2021-10-09

1、前言

這是《spring Cloud 進階》專欄的第五篇文章,這篇文章介紹一下阿里開源的流量防衛兵Sentinel,一款非常優秀的開源專案,經過近10年的雙十一的考驗,非常成熟的一款產品。往期文章如下:

文章目錄如下:

2、什麼是sentinel?

sentinel顧名思義:衛兵;在Redis中叫做哨兵,用於監控主從切換,但是在微服務中叫做流量防衛兵

Sentinel 以流量為切入點,從流量控制熔斷降級系統負載保護等多個維度保護服務的穩定性。

Sentinel 具有以下特徵:

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、訊息削峰填谷、叢集流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制檯中看到接入應用的單臺機器秒級資料,甚至 500 臺以下規模的叢集的彙總執行情況。
  • 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模組,例如與 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。同時 Sentinel 提供 Java/Go/C++ 等多語言的原生實現。
  • 完善的 SPI 擴充套件機制:Sentinel 提供簡單易用、完善的 SPI 擴充套件介面。您可以通過實現擴充套件介面來快速地定製邏輯。例如定製規則管理、適配動態資料來源等。

Sentinel 的主要特性如下圖

Sentinel 分為兩個部分:

  • 核心庫(Java 客戶端)不依賴任何框架/庫,能夠執行於所有 Java 執行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支援。
  • 控制檯(Dashboard)基於 Spring Boot 開發,打包後可以直接執行,不需要額外的 Tomcat 等應用容器。

總之一句話:sentinel真牛逼,完爆Hystrix.........

3、sentinel和Hystrix有何區別?

不多說了,總之一句話:Hystrix趕緊放棄,用sentinel......

具體區別如下圖:

4、sentinel版本如何選擇?

由於陳某寫的是Spring Cloud 進階一個系列,使用的聚合專案,因此版本還是保持和之前文章一樣,不清楚的可以看這篇:五十五張圖告訴你微服務的靈魂擺渡者Nacos究竟有多強?

這裡選擇的spring-cloud-alibaba-dependencies的版本是2.2.1.RELEASE,因此sentinel版本選擇1.7.1,大家可以根據自己的版本選擇對應sentinel的版本,版本對應關係如下圖:

注意:一定要按照官方推薦的版本適配,否則出現意想不到的BUG追悔莫及.........

5、Sentinel 控制檯如何安裝?

sentinel和nacos一樣,都有一個控制檯,但是這裡不用自己手動搭建一個微服務,官方已經搭建好了,只需要下載對應得jar包執行即可。下載地址:https://github.com/alibaba/Sentinel/tags

選擇對應得版本下載即可,我這裡選擇1.7.1版本,下載的jar包如下圖:

當然你可以通過原始碼構建:mvn clean package

注意:JDK版本必須>=1.8

此時我們只需要執行這個jar包即可,命令如下:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar

上述引數含義如下:

  • -Dserver.port:指定啟動的埠,預設8080

  • -Dproject.name:指定本服務的名稱

  • -Dcsp.sentinel.dashboard.server:指定sentinel控制檯的地址,用於將自己註冊進入實現監控自己

啟動成功之後,瀏覽器訪問:http://localhost:8080,登入頁面如下圖:

預設的使用者名稱和密碼:sentinel/sentinel

登入成功之後頁面如下:

可以看到目前只有一個服務sentinel-dashboard被監控了,這個服務就是自己

注意:上述引數都是可選的,沒必要可以不填。

那麼問題來了:預設的使用者名稱和密碼在生產環境上肯定不能用,如何修改呢?

從 Sentinel 1.6.0 起sentinel已經支援自定義使用者名稱和密碼了,只需要在執行jar命令時指定即可,命令如下:

java -Dsentinel.dashboard.auth.username=admin -Dsentinel.dashboard.auth.password=123 -jar sentinel-dashboard-1.7.1.jar

使用者可以通過如下引數進行配置:

  • -Dsentinel.dashboard.auth.username=sentinel 用於指定控制檯的登入使用者名稱為 sentinel
  • -Dsentinel.dashboard.auth.password=123456 用於指定控制檯的登入密碼為 123456;如果省略這兩個引數,預設使用者和密碼均為 sentinel
  • -Dserver.servlet.session.timeout=7200 用於指定 Spring Boot 服務端 session 的過期時間,如 7200 表示 7200 秒;60m 表示 60 分鐘,預設為 30 分鐘;

注意:部署多臺控制檯時,session 預設不會在各例項之間共享,這一塊需要自行改造。

除了使用者名稱密碼相關的配置,sentinel控制檯還提供了其他的可配置選項,如下圖:

6、微服務如何接入sentinel控制檯?

微服務為什麼要整合sentinel控制檯,sentinel不是提供了相關的API嗎?

其實Spring Boot 官方一直提倡約定>配置>編碼的規則,能夠不硬編碼何樂而不為呢?

因此本文後續內容主要還是結合sentinel控制檯進行講解,關於API的使用大家可以按照官方文件學習,講解的非常清楚。

好了,言歸正傳,微服務如何接入sentinel控制檯呢?

1、新建微服務模組註冊進入Nacos

這裡的註冊中心依然使用的是nacos,有不會用的請看專欄第一篇Nacos文章:五十五張圖告訴你微服務的靈魂擺渡者Nacos究竟有多強?

新建一個微服務模組:sentinel-service9008,相關程式碼不貼出了。

相關配置如下:

server:
  port: 9008
spring:
  application:
    ## 指定服務名稱,在nacos中的名字
    name: sentinel-service
  cloud:
    nacos:
      discovery:
        # nacos的服務地址,nacos-server中IP地址:埠號
        server-addr: 127.0.0.1:8848
management:
  endpoints:
    web:
      exposure:
        ## yml檔案中存在特殊字元,必須用單引號包含,否則啟動報錯
        include: '*'

原始碼全部會上傳,獲取方式看文末!

2、新增依賴

除了Nacos的依賴,還需要新增一個sentinel的依賴:

<!--sentinel的依賴-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

以上只貼出了sentinel相關依賴,nacos依賴不再貼了,見原始碼!

3、新增配置整合控制檯

只需要新增如下配置即可整合sentinel控制檯:

spring:
  cloud:
    sentinel:
      transport:
      	## 指定控制檯的地址,預設埠8080
        dashboard: localhost:8080

4、新建一個測試介面

下面新建一個測試介面,用於測試相關規則,如下:

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

    @GetMapping("/test")
    public String test(){
        return "接收到一條訊息--------";
    }
}

5、啟動微服務

啟動9008這個微服務,然後瀏覽器輸入:http://localhost:9008/sentinel/test,此時檢視sentinel控制檯,將會看見sentinel-service這個服務已經被監控了,如下圖:

注意:sentinel是懶載入機制,只有訪問過一次的資源才會被監控。

不過可以通過配置關閉懶載入,在專案啟動時就連線sentinel控制檯,配置如下:

spring:
    sentinel:
      # 取消控制檯懶載入,專案啟動即連線Sentinel
      eager: true

7、流量控制如何配置?

流量控制(flow control),其原理是監控應用流量的 QPS併發執行緒數等指標,當達到指定的閾值時對流量進行控制,以避免被瞬時的流量高峰沖垮,從而保障應用的高可用性

QPS:每秒請求數,即在不斷向伺服器傳送請求的情況下,伺服器每秒能夠處理的請求數量。

併發執行緒數:指的是施壓機施加的同時請求的執行緒數量。

同一個資源可以建立多條限流規則,一條限流規則由以下元素組成:

  • resource:資源名,即限流規則的作用物件。
  • count: 限流閾值
  • grade:限流閾值型別(1:QPS 0:併發執行緒數),預設值QPS
  • limitApp:流控針對的呼叫來源,若為 default 則不區分呼叫來源,預設值default
  • strategy:判斷的根據是資源自身(0),還是根據其它關聯資源 (1),還是根據鏈路入口(2),預設值根據資源本身。
  • controlBehavior: 流控效果(直接拒絕(0) / 排隊等待(2) / 預熱冷啟動(1)),預設值直接拒絕。

以上元素限流元素對應的類是com.alibaba.csp.sentinel.slots.block.flow.FlowRule,各元素如下圖:

注意:各個元素的取值以及預設值一定要記住,後續配置將會用到。

以上幾個元素在sentinel控制檯對應規則如下圖:

1、三種流控效果

流控效果總共分為三種,對應元素controlBehavior,分別如下:

快速失敗

預設的流量控制方式,當QPS超過任意規則的閾值後,新的請求就會被立即拒絕,拒絕方式為丟擲FlowException

warm up

預熱/冷啟動方式。當系統長期處於低水位的情況下,當流量突然增加時,直接把系統拉昇到高水位可能瞬間把系統壓垮。通過"冷啟動",讓通過的流量緩慢增加,在一定時間內逐漸增加到閾值上限,給冷系統一個預熱的時間,避免冷系統被壓垮。

注意:這一效果只針對QPS流控,併發執行緒數流控不支援。

預熱底層是根據令牌桶演算法實現的,原始碼對應得類在com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

演算法中有一個冷卻因子coldFactor,預設值是3,即請求 QPS 從 threshold(閾值) / 3 開始,經預熱時長逐漸升至設定的 QPS 閾值。

比如設定QPS閾值為3,流控效果為warm up,預熱時長為5秒,如下圖:

這樣配置之後有什麼效果呢:QPS起初會從(3/3/=1)每秒通過一次請求開始預熱直到5秒之後達到每秒通過3次請求。動態效果圖如下:

從上述動畫可以清楚的看見:前幾秒是頻繁流控的,直到5秒,QPS閾值達到了3。

具體演算法原理請看:https://github.com/alibaba/Sentinel/wiki/限流---冷啟動

排隊等待

勻速排隊方式會嚴格控制請求通過的間隔時間,也即是讓請求以均勻的速度通過,對應的是漏桶演算法。原始碼對應得類:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

注意:這一效果只針對QPS流控,併發執行緒數流控不支援。

簡單舉個例子:你去大學食堂吃飯,只有一個阿姨在打飯,那麼所有人都要排隊打飯,每次只有一個人打到飯,其他人都在排隊等待。

不同的是sentinel有個超時等待時間,一旦超過這個預定設定的時間將會被限流。

該方式作用如下圖:

這種方式適合用於請求以突刺狀來到,這個時候我們不希望一下子把所有的請求都通過,這樣可能會把系統壓垮;同時我們也期待系統以穩定的速度,逐步處理這些請求,以起到“削峰填谷”的效果,而不是拒絕所有請求。

比如設定QPS閾值為1,超時等待時間為10000毫秒,如下圖:

此時的效果如下:

從上圖可以看到:連續點選重新整理請求,雖然設定了QPS閾值為1,但是並沒有被限流,而是在等待,因為設定了超時等待時間為10秒。

具體演算法原理請看:https://github.com/alibaba/Sentinel/wiki/流量控制-勻速排隊模式

2、三種流控模式

流控模式總共分為三種,對應元素strategy,分別如下:

  • 直接拒絕:介面達到限流條件時,直接限流
  • 關聯:當關聯的資源達到閾值時,就限流自己
  • 鏈路:只記錄指定鏈路上的流量(指定資源從入口資源進來的流量,如果達到閾值,就可以限流)

下面來詳細介紹下以上三種流控模式。

直接拒絕

顧名思義:預設的流量控制方式,當QPS超過任意規則的閾值後,新的請求就會被立即拒絕,拒絕方式為丟擲FlowException。上面的幾個例子都是配置了直接拒絕這個模式,這裡不再詳細介紹。

關聯

典型的使用場景:一個是支付介面,一個是下單介面,此時一旦支付介面達到了閾值,那麼訂單介面就應該被限流,不然這邊還在下單,消費者等待或者直接被拒絕支付將會極大的影響使用者體驗。

簡而言之:A關聯B,一旦B達到閾值,則A被限流

演示一下效果,建立以下兩個介面:

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

    /**
     * 下單介面
     * @return
     */
    @GetMapping("/order")
    public String order()  {
        return "下單成功..........";
    }

    /**
     * 支付介面
     * @return
     */
    @GetMapping("/pay")
    public String pay()  {
        return "支付成功..........";
    }
}

此時的流控規則配置如下圖:

注意:關聯之後,這裡設定的限流規則是對被關聯資源,也就是/sentinel/pay這個資源,但是真正被限流則是/sentinel/order

如何演示效果呢?很簡單,只需要不斷的請求/sentinel/pay達到閾值,然後在請求/sentinel/order

利用POSTMAN不斷向/sentinel/pay發出請求,然後瀏覽器請求/sentinel/order,結果如下圖:

可以看到訂單介面被限流了.............

3、兩種統計型別

流控分為兩種統計型別,分別是QPS併發執行緒數,很多人不太明白這兩種統計型別有什麼區別?

舉個例子:陳某帶了一個億去銀行存錢,但是銀行大門保安要查健康碼,每秒最多隻能同時進入4個人,並且銀行中只有兩個工作人員工作,如下圖:

此時的QPS含義:從保安到銀行這一段,即是保安放行進入銀行的人數。

此時併發執行緒數的含義:銀行只有兩個工作人員在工作,那麼最多隻能同時處理兩個任務,這裡併發執行緒數的閾值就是2。

8、降級規則如何配置?

熔斷降級在日常生活中也是比較常見的,場景如下:

  • 股票市場的熔斷,當價格觸發到了熔點之後,會暫停交易一段時間,或者交易可以繼續進行,但是報價會限制在一定的範圍。
  • 電壓過高導致保險絲觸發熔斷保護

在大型的分散式系統中,一個請求的依賴如下圖:

如果這個時候,某個服務出現一些異常,比如:

  • 服務提供者不可用(硬體故障、程式bug、網路故障、使用者請求量較大)
  • 重試導致的流量過大
  • 服務呼叫者使用同步呼叫,產生大量的等待執行緒佔用系統資源,一旦執行緒資源被耗盡,呼叫者提供的服務也會變成不可用狀態

那麼將會導致整個服務不可用,用古話來講就是:千里之堤毀於蟻穴

所謂程式設計源於生活,架構師們根據生活的經驗設計出了服務的熔斷降級策略,很好的解決了這類問題。

熔斷降級規則對應sentinel控制檯的降級規則這一欄,如下圖:

熔斷降級涉及到的幾個屬性如下表:

原始碼中對應得類為:com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule

三種熔斷策略

Sentinel 提供以下幾種熔斷策略:

  1. 平均響應時間 (DEGRADE_GRADE_RT):當 1s 內持續進入 5 個請求,對應時刻的平均響應時間(秒級)均超過閾值(count,以 ms 為單位),那麼在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這個方法的呼叫都會自動地熔斷(丟擲 DegradeException)。注意 Sentinel 預設統計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置。
  2. 異常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):當資源的每秒請求量 >= 5,並且每秒異常總數佔通過量的比值超過閾值(DegradeRule 中的 count)之後,資源進入降級狀態,即在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這個方法的呼叫都會自動地返回。異常比率的閾值範圍是 [0.0, 1.0],代表 0% - 100%。
  3. 異常數 (DEGRADE_GRADE_EXCEPTION_COUNT):當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。注意由於統計時間視窗是分鐘級別的,若 timeWindow 小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態。

下面演示一個平均響應時間熔斷,建立一個介面,如下:

@RestController
@RequestMapping("/sentinel/provider")
@Slf4j
public class FlowLimitController {

    @GetMapping("/test")
    public String test() throws InterruptedException {
        //休眠3秒鐘
        Thread.sleep(3000);
        log.info("收到一條訊息----test");
        return "接收到一條訊息--------";
    }
}

在控臺為這個介面設定平均響應時間為200毫秒,時間視窗為1秒,大致意思:平均的響應時間大於200毫秒之後,在接下來的1秒時間內將會直接熔斷,如下圖:

使用Jmeter開啟10個執行緒迴圈跑,然後在瀏覽器中訪問這個介面,返回結果如下圖:

為什麼呢?由於的介面中休眠了3秒,平均響應時間肯定大於200毫秒,因此直接被熔斷了。

注意:這裡熔斷後直接返回預設的資訊,後面會介紹如何定製熔斷返回資訊。

9、熱點引數如何限流?

顧名思義:熱點就是經常訪問的資料,很多時候肯定是希望統計某個訪問頻次Top K資料並對其進行限流。

比如秒殺系統中的商品ID,對於熱點商品那一瞬間的併發量是非常可怕的,因此必須要對其進行限流。

Sentinel 利用 LRU 策略統計最近最常訪問的熱點引數,結合令牌桶演算法來進行引數級別的流控。

注意:熱點引數限流只針對QPS。

官方文件:https://github.com/alibaba/Sentinel/wiki/熱點引數限流

概念理解了,來看下sentinel控制檯如何設定熱點引數限流,如下圖:

規則對應得原始碼在com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule這個類中,各種屬性含義如下圖:

規則都懂了,下面我們通過實戰來演示一下熱點引數到底是如何限流的。

注意:熱點引數限流只作用於八大基本型別。

1、建立一個資源

現在先建立一個service,用@SentinelResource這個註解定義一個資源,這個註解後續將會詳細介紹,先忽略,程式碼如下:

@Service
@Slf4j
public class FlowServiceImpl implements FlowService {

    /**
     * @SentinelResource的value屬性指定了資源名,一定要唯一
     * blockHandler屬性指定了兜底方法
     */
    @Override
    @SentinelResource(value = "OrderQuery",blockHandler = "handlerQuery")
    public String query(String p1, String p2) {
        log.info("查詢商品,p1:{},p2:{}",p1,p2);
        return "查詢商品:success";
    }

    /**
     * 對應得兜底方法,一旦被限流將會呼叫這個方法來處理
     */
    public String handlerQuery(@RequestParam(value = "p1",required = false) String p1,
                               @RequestParam(value = "p2",required = false)String p2,
                               BlockException exception){
        log.info("查詢商品,p1:{},p2:{}",p1,p2);
        return "查詢商品:熔斷了......";
    }
}

上述程式碼什麼意思呢?如下:

  • 如果query這個介面沒有被限流則返回:查詢商品:success
  • 如果query這個介面被限流了,則進入了兜底方法handlerQuery方法,返回:查詢商品:熔斷了......

2、建立controller介面

下面建立一個controller進行測試,程式碼如下:

@RestController
@RequestMapping("/sentinel/provider")
@Slf4j
public class FlowLimitController {
    @Autowired
    private FlowService flowService;

    @GetMapping("/order/query")
    public String query(@RequestParam(value = "p1",required = false) String p1, @RequestParam(value = "p2",required = false)String p2){
        return flowService.query(p1,p2);
    }

}

可以看到介面中有兩個引數,分別是p1p2

3、新增熱點引數限流規則

在sentinel控制檯點選熱點規則->新增熱點限流規則,新增如下圖規則:

上述配置的具體含義:當OrderQuery這個資源中的第0個引數QPS超過1秒1次將會被限流。這裡引數索引是從0開始,第0個就是對應介面中的p1這個引數。

第一個測試:瀏覽器直接訪問:http://localhost:9009/sentinel/provider/order/query?p1=22&p2=1222,連續點選將會看到這個介面被熔斷降級了,如下圖:

這也正是驗證了上述的熱點引數限流配置。

第二個測試:瀏覽器輸入:http://localhost:9009/sentinel/provider/order/query?p2=1222,連續點選將會看到這個介面並沒有被熔斷降級,如下圖:

注意:對於熱點引數限流,只有包含指定索引的引數請求才會被限流,否則不影響。

此時產品說:ID為100的這個產品點選量太少了,你們趕緊調整下這個商品的限流規則。這個時候該怎麼辦呢?

彆著急,sentinel顯然考慮到了這一點,提供了引數例外項這項配置,針對產品需求配置如下:

從上圖配置中,我們將引數值p1這個引數值等於100的時候,限流閾值設定成了100,也就是說p1=100這個請求QPS放寬到1秒請求100次以上才會被限流。

驗證:瀏覽器輸入地址:http://localhost:9009/sentinel/provider/order/query?p1=100,無論點選多麼快,都沒有被熔斷降級,顯然是配置生效了,如下圖:

以上原始碼在sentinel-openfeign-provider9009這個模組中,文末有原始碼獲取方式。

10、系統自適應如何限流?

前面熱點引數、普通流量限流都是針對的某個介面,這裡系統自適應限流針對是整個系統的入口流量,從單臺機器的 loadCPU 使用率平均 RT入口 QPS併發執行緒數等幾個維度監控應用指標,讓系統儘可能跑在最大吞吐量的同時保證系統整體的穩定性。

sentinel控制檯對應如下圖:

閾值型別有五種,分別如下:

  • Load 自適應(僅對 Linux/Unix-like 機器生效):系統的 load1 作為啟發指標,進行自適應系統保護。當系統 load1 超過設定的啟發值,且系統當前的併發執行緒數超過估算的系統容量時才會觸發系統保護(BBR 階段)。系統容量由系統的 maxQps * minRt 估算得出。設定參考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):當系統 CPU 使用率超過閾值即觸發系統保護(取值範圍 0.0-1.0),比較靈敏。
  • 平均 RT:當單臺機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。
  • 併發執行緒數:當單臺機器上所有入口流量的併發執行緒數達到閾值即觸發系統保護。
  • 入口 QPS:當單臺機器上所有入口流量的 QPS 達到閾值即觸發系統保護。

官方文件:https://github.com/alibaba/Sentinel/wiki/系統自適應限流

系統規則的配置比較簡單,這裡以入口QPS為例進行演示,為了演示真實情況,清掉所有的限流規則,新增系統規則,如下圖:

這個QPS系統規則一配置,該微服務中的所有介面都將會被這個規則限制,比如訪問:http://localhost:9009/sentinel/provider/pay,連續點選,如下圖:

阿里限流神器Sentinel奪命連環 17 問?

可以看到已經被限流了,不僅是這個介面,所有介面都會生效。

注意:系統規則中的入口QPS這個規則不建議配置,一旦配置上了可能導致整個服務不可用。

11、如何自定義限流返回的異常資訊?

在前面的例子中,無論是熔斷降級還是被限流返回的異常資訊都是Blocked by Sentinel (flow limiting),這個是Sentinel預設的異常資訊。

很顯然預設的異常資訊並不能滿足我們的業務需求,因此我們需要根據前後端規則制定自己的異常返回資訊。

這裡將會用到一個註解@SentinelResource,這個在上文也是提到過,這個註解中有兩個關於限流兜底方法的屬性,如下:

  • blockHandler: 對應處理 BlockException 的函式名稱。blockHandler 函式訪問範圍需要是 public,返回型別需要與原方法相匹配,引數型別需要和原方法相匹配並且最後加一個額外的引數,型別為 BlockException。blockHandler 函式預設需要和原方法在同一個類中。
  • blockHandlerClass:指定 blockHandlerClass 為對應的類的 Class 物件,注意對應的函式必需為 static 函式,否則無法解析。

官方文件:https://github.com/alibaba/Sentinel/wiki/註解支援

使用@SentinelResource註解自定義一個限流異常返回資訊,先自定義一個資源,指定兜底方法為handler,程式碼如下:

第二步:寫個對應得兜底方法,必須在同一個類中,程式碼如下:

第三步:對資源QueryOrder新增一個限流規則,如下圖:

第四步:寫個controller,程式碼就不曬了,自己寫吧,哈哈。。。。

第五步:呼叫介面,瘋狂點選,將會出現兜底方法中定義的返回資訊,如下圖:

到這兒基本算是成功了,但是有個問題:兜底方法必須要和業務方法放在同一個類中,這樣程式碼耦合度不是很高嗎?

@SentinelResource提供一個屬性blockHandlerClass,完美的解決了這一個問題,能夠將兜底方法單獨放在一個類中,下面來介紹一下。

第一步:新建一個單獨的類CommonHandler來放置兜底方法,程式碼如下:

第二步:在@SentinelResource註解中指定blockHandlerClass為上面的類,blockHandler指定兜底方法名,程式碼如下:

好了,至此就完成了,自己照著試試吧.......

上述原始碼在sentinel-openfeign-provider9009這個模組中,原始碼獲取方式見文末。

12、如何對異常進行降級處理?

程式設計師每天都在製造BUG,沒有完美的程式碼,也沒有完美的程式設計師,針對程式碼的執行時異常我們無法避免,但是我們可以當出現異常的時候進行捕獲並做出相應的處理,我們稱之為降級處理。

異常的降級還是要用到@SentinelResource註解,其中相關的幾個屬性如下:

  • fallback:fallback 函式名稱,可選項,用於在丟擲異常的時候提供 fallback 處理邏輯。fallback 函式可以針對所有型別的異常(除了 exceptionsToIgnore 裡面排除掉的異常型別)進行處理。fallback 函式簽名和位置要求:
    • 返回值型別必須與原函式返回值型別一致;
    • 方法引數列表需要和原函式一致,或者可以額外多一個 Throwable 型別的引數用於接收對應的異常。
    • fallback 函式預設需要和原方法在同一個類中。若希望使用其他類的函式,則可以指定 fallbackClass 為對應的類的 Class 物件
  • 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)進行處理,不能針對業務異常進行處理

官方文件:https://github.com/alibaba/Sentinel/wiki/註解支援

下面定義一個建立訂單的介面,手動製造一個1/0異常,程式碼如下:

上述介面並沒有進行異常降級處理,因此呼叫該介面直接返回了異常資訊,非常不友好,如下圖:

我們可以使用fallback指定異常降級的兜底方法,此時業務方法改造如下:

使用fallbackClass屬性指定單獨一個類處理異常降級,降低了程式碼的耦合度,fallback屬性指定了降級兜底的方法,程式碼如下:

此時再次訪問介面,雖然有異常,但是返回的確實降級兜底方法中的返回資訊,如下圖:

到了這裡基本滿足了異常降級的處理需求,但是仍然有個疑問:能否只用一個方法處理全部的異常?

答案是:必須能,此時就要用到defaultFallback 這個屬性了,指定預設的降級兜底方法,此時的業務方法變成如下程式碼:

defaultFallback屬性指定了預設的降級兜底方法,這個方法程式碼如下:

好了,異常降級處理到這兒已經介紹完了,但是仍然有一個問題:若 blockHandler 和 fallback 都進行了配置,那麼哪個會生效?

結論:若 blockHandler 和 fallback 都進行了配置,則被限流降級而丟擲 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandlerfallbackdefaultFallback,則被限流降級時會將 BlockException 直接丟擲

createOrder這個業務介面改造一下,同時指定blockHandler和fallback,程式碼如下:

此時不配置任何規則,直接訪問介面,可以看到這裡直接進入了異常降級處理,如下圖:

我們對createOrder這個資源配置降級規則:60秒內如果出現2個以上的異常直接限流,如下圖:

此時我們再次訪問這個介面,可以看到前兩次直接進入了fallback指定的方法中(並未達到限流的異常數閾值),兩次之後就被限流了,進入了blockHandler方法中,效果如下圖:

上述原始碼在sentinel-openfeign-provider9009這個模組中,原始碼獲取方式見文末。

13、sentinel的黑白名單如何設定?

顧名思義,黑名單就是拉黑唄,拉黑就是不能訪問了唄,sentinel能夠針對請求來源進行是否放行,若配置白名單則只有請求來源位於白名單內時才可通過;若配置黑名單則請求來源位於黑名單時不通過,其餘的請求通過。

sentinel控制檯對應得規則配置如下圖:

該規則對應得原始碼為com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule,幾個屬性如下:

  • resource:資源名,即限流規則的作用物件。
  • limitApp:對應的黑名單/白名單,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 為白名單模式,AUTHORITY_BLACK 為黑名單模式,預設為白名單模式。

官方文件:https://github.com/alibaba/Sentinel/wiki/黑白名單控制

這裡有個問題:請求來源是什麼,怎麼獲取?

Sentinel提供了一個介面RequestOriginParser,我們可以實現這個介面根據自己業務的規則解析出請求來源名稱。

下面我以IP作為區分請求來源,程式碼如下:

然後將127.0.0.1設定為黑名單,如下圖:

直接訪問:http://127.0.0.1:9009/sentinel/rate/order/query?id=1002,結果如下圖:

可以看到被限流了哦.................

好了,黑白名單就介紹到這裡。

上述原始碼在sentinel-openfeign-provider9009這個模組中,原始碼獲取方式見文末。

14、限流規則如何持久化?

Sentinel預設限流規則是儲存在記憶體中,只要服務重啟之後對應得限流規則也會消失,實際的生產中肯定是不允許這種操作,因此限流規則的持久化迫在眉睫。

sentinel官方文件提供了兩種持久化模式,分別如下:

但是官方推薦使用Push模式,下面陳某就Push模式介紹一下持久化限流規則。這裡使用Nacos作為配置中心。

盜用官方一張架構圖,如下:

1、新增依賴

這裡需要新增一個依賴,如下:

<dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2、配置檔案中配置相關資訊

既然使用到了Nacos作為配置中心,肯定是要配置相關的地址、dataId...

application.yml配置檔案中新增如下配置:

spring:
  cloud:
    sentinel:
      ## nacos持久化配置
      datasource:
        ## 配置流控規則,名字任意
        ds-flow:
          nacos:
            ## nacos的地址
            server-addr: 127.0.0.1:8848
            ## 配置ID
            dataId: ${spring.application.name}-flow
            ## 配置分組,預設是DEFAULT_GROUP
            groupId: DEFAULT_GROUP
            ## 配置儲存的格式
            data-type: json
            ## rule-type設定對應得規則型別,總共七大型別,在com.alibaba.cloud.sentinel.datasource.RuleType這個列舉類中有體現
            rule-type: flow
        ## 配置降級規則,名字任意
        ds-degrade:
          nacos:
            ## nacos的地址
            server-addr: 127.0.0.1:8848
            ## 配置ID
            dataId: ${spring.application.name}-degrade
            ## 配置分組,預設是DEFAULT_GROUP
            groupId: DEFAULT_GROUP
            ## 配置儲存的格式
            data-type: json
            ## rule-type設定對應得規則型別,總共七大型別,在com.alibaba.cloud.sentinel.datasource.RuleType這個列舉類中有體現
            rule-type: degrade

上述配置僅僅展示了和持久化相關的一些配置,其他相關的配置程式碼就不貼了,稍後自己看原始碼。

spring.cloud.sentinel.datasource下可以配置多個規則,陳某這裡只配置了限流和降級規則,其他規則自己嘗試配一下,不同規則通過rule-type區分,其取值都在com.alibaba.cloud.sentinel.datasource.RuleType這個列舉類中,對應著sentinel中的幾大統計規則。

3、在Nacos新增對應的規則配置

上述配置中對應的限流(flow)規則如下圖:

上述配置中對應的降級(degrade)規則如下圖:

先不糾結JSON資料裡面到底是什麼,先看效果,全部發布之後,Nacos中總共有了兩個配置,如下圖:

上圖中可以看到我們的兩種規則已經在Nacos配置好了,來看一下sentinel中是否已經生效了,如下圖:

哦了,已經生效了,由於是push模式,只要nacos中點選發布配置,相關規則配置就會推送到sentinel中。

上述原始碼在sentinel-openfeign-provider9009這個模組中,原始碼獲取方式見文末。

伏筆:push模式只能保證Nacos中的修改推送到sentinel控制檯,但是sentinel控制檯的限流規則修改如何推送到Nacos呢?彆著急,下面將會介紹..............

4、JSON中到底怎麼寫?

很多人好奇JOSN中的配置到底怎麼寫?其實很簡單,陳某在介紹各種規則的時候都明確告訴你每種規則對應原始碼中的實現類,比如流控規則對應的類就是com.alibaba.csp.sentinel.slots.block.flow.FlowRule,JOSN中各個屬性也是來源於這個類。

下面陳某列出各個規則的JSON配置,開發中照著改即可。

1、流控規則

[
  {
    // 資源名
    "resource": "/test",
    // 針對來源,若為 default 則不區分呼叫來源
    "limitApp": "default",
    // 限流閾值型別(1:QPS;0:併發執行緒數)
    "grade": 1,
    // 閾值
    "count": 1,
    // 是否是叢集模式
    "clusterMode": false,
    // 流控效果(0:快速失敗;1:Warm Up(預熱模式);2:排隊等待)
    "controlBehavior": 0,
    // 流控模式(0:直接;1:關聯;2:鏈路)
    "strategy": 0,
    // 預熱時間(秒,預熱模式需要此引數)
    "warmUpPeriodSec": 10,
    // 超時時間(排隊等待模式需要此引數)
    "maxQueueingTimeMs": 500,
    // 關聯資源、入口資源(關聯、鏈路模式)
    "refResource": "rrr"
  }
]

2、降級規則

[
  {
  	// 資源名
    "resource": "/test1",
    "limitApp": "default",
    // 熔斷策略(0:慢呼叫比例,1:異常比率,2:異常計數)
    "grade": 0,
    // 最大RT、比例閾值、異常數
    "count": 200,
    // 慢呼叫比例閾值,僅慢呼叫比例模式有效(1.8.0 引入)
    "slowRatioThreshold": 0.2,
    // 最小請求數
    "minRequestAmount": 5,
    // 當單位統計時長(類中預設1000)
    "statIntervalMs": 1000,
    // 熔斷時長
    "timeWindow": 10
  }
]

3、熱點規則

[
  {
  	// 資源名
    "resource": "/test1",
    // 限流模式(QPS 模式,不可更改)
    "grade": 1,
    // 引數索引
    "paramIdx": 0,
    // 單機閾值
    "count": 13,
    // 統計視窗時長
    "durationInSec": 6,
    // 是否叢集 預設false
    "clusterMode": 預設false,
    // 
    "burstCount": 0,
    // 叢集模式配置
    "clusterConfig": {
      // 
      "fallbackToLocalWhenFail": true,
   	  // 
      "flowId": 2,
      // 
      "sampleCount": 10,
      // 
      "thresholdType": 0,
      // 
      "windowIntervalMs": 1000
    },
    // 流控效果(支援快速失敗和勻速排隊模式)
    "controlBehavior": 0,
    // 
    "limitApp": "default",
    // 
    "maxQueueingTimeMs": 0,
    // 高階選項
    "paramFlowItemList": [
      {
      	// 引數型別
        "classType": "int",
      	// 限流閾值
        "count": 222,
      	// 引數值
        "object": "2"
      }
    ]
  }
]

4、系統規則

負值表示沒有閾值檢查。不需要刪除引數

[
  {
  	// RT
    "avgRt": 1,
    // CPU 使用率
    "highestCpuUsage": -1,
    // LOAD
    "highestSystemLoad": -1,
    // 執行緒數
    "maxThread": -1,
    // 入口 QPS
    "qps": -1
  }
]

5、授權規則

[
  {
    // 資源名
    "resource": "sentinel_spring_web_context",
  	// 流控應用
    "limitApp": "/test",
    // 授權型別(0代表白名單;1代表黑名單。)
    "strategy": 0
  }
]

注意:對於上述JOSN中的一些可選屬性不需要的時候可以刪除。

官方文件:https://github.com/alibaba/Sentinel/wiki/在生產環境中使用-Sentinel

15、限流規則如何推送到Nacos進行持久化?

sentinel預設的持久化只能從nacos推送到sentinel控制檯,但是實際生產中肯定是雙向修改都能推送的,這個如何解決呢?

其實sentinel官方文件就有說到解決方法,不過需要自己修改sentinel控制檯的原始碼來實現。

這個還是比較複雜的,sentinel只幫我們實現了流控規則的demo,其他的還是要自己修改,這點不太人性化....

在這之前需要自己下載對應版本的sentinel控制檯的原始碼,地址:https://github.com/alibaba/Sentinel/tags

流控規則原始碼修改

在原始碼的test目錄下有sentinel提供的demo,分別有apollo、nacos、zookeeper,如下圖:

這裡我們是Nacos,因此只需要nacos包下面的demo。修改步驟如下:

1、去掉sentinel-datasource-nacos依賴的scop

這個sentinel-datasource-nacos依賴預設是<scope>test</scope>,因此我們需要去掉這個,如下:

<!-- for Nacos rule publisher sample -->
<dependency>
       <groupId>com.alibaba.csp</groupId>
       <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

如果你整合的zookeeper或者apollo,則把相應的依賴也要修改。

2、複製test環境下的nacos整個包到main下

將這個nacos包複製到com.alibaba.csp.sentinel.dashboard.rule這個包下,如下圖:

3、將FlowControllerV2中的程式碼複製到FlowControllerV1中

com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2這個是sentinel提供的demo,只需要將其中的程式碼全部覆蓋到com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1中。

4、修改FlowControllerV1中的程式碼

直接覆蓋掉當然不行,還要做一些修改,如下:

  • 修改RequestMapping中的請求url為/v1/flow
  • 修改ruleProviderrulePublisher的依賴,修改後的程式碼如下:
	@Autowired
    //使用nacos的依賴
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    //使用nacos的依賴
    @Qualifier("flowRuleNacosPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

5、注意nacos的相關配置

com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfigUtil這個工具類中對應的是限流規則在nacos中的一些配置項,有groupIddataId...對應的配置如下:

需要兩邊統一,可以自己修改。

com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfig這個類中有個方法如下圖:

預設指定的nacos地址是本地的,這個需要修改。

6、完成

以上步驟已經改造了sentinel控制檯的流控規則,打包啟動控制檯程式碼,命令如下:

mvn clean install -DskipTests=true -pl sentinel-dashboard -am

啟動後在控制檯新增流控規則,可以看到也會同步推送到nacos,包括增刪改。

其他規則修改也很簡單,照葫蘆畫瓢,這裡就不再詳細說了,後面會單獨出一篇文章詳細說一下。

16、叢集流控如何做?

首先一個簡單的問題:為什麼需要叢集流控?單機流控不香嗎?原因如下:

  • 對於微服務要想保證高可用,必須是叢集,假設有100個叢集,那麼想要設定流控規則,是不是每個微服務都要設定一遍?維護成本太高了
  • 單體流控還會造成流量不均勻的問題,出現總流控閾值沒有達到某些微服務已經被限流了,這個是非常糟糕的問題,因此實際生產中對於叢集不推薦單體流控。

那麼如何解決上述的問題呢?sentinel為我們提供了叢集流控的規則。思想很簡單就是提供一個專門的server來統計呼叫的總量,其他的例項都與server保持通訊。

叢集流控可以精確地控制整個叢集的呼叫總量,結合單機限流兜底,可以更好地發揮流量控制的效果。

叢集流控中共有兩種身份:

  • Token Client:叢集流控客戶端,用於向所屬 Token Server 通訊請求 token。叢集限流服務端會返回給客戶端結果,決定是否限流。
  • Token Server:即叢集流控服務端,處理來自 Token Client 的請求,根據配置的叢集規則判斷是否應該發放 token(是否允許通過)。

sentinel的叢集限流有兩種模式,分別如下:

  • 獨立模式(Alone):即作為獨立的 token server 程式啟動,獨立部署,隔離性好,但是需要額外的部署操作。獨立模式適合作為 Global Rate Limiter 給叢集提供流控服務。
  • 嵌入模式(Embedded):即作為內建的 token server 與服務在同一程式中啟動。在此模式下,叢集中各個例項都是對等的,token server 和 client 可以隨時進行轉變,因此無需單獨部署,靈活性比較好。但是隔離性不佳,需要限制 token server 的總 QPS,防止影響應用本身。嵌入模式適合某個應用叢集內部的流控。

下面就以嵌入模式為例介紹一下如何配置。

就以sentinel-openfeign-provider9009這個模組作為演示,直接啟動三個叢集,埠分別為900990119013,如下圖:

啟動成功,在sentinel控制檯將會看到有三個例項已經被監控了,如下圖:

此時只需要在控制檯指定一個服務為token server,其他的為token client,叢集流控->新增token server,操作如下圖:

選取一個作為服務端,另外兩個作為客戶端,此時就已經配置好了,如下圖:

此時就可以新增叢集流控規則了,可以在sentinel控制檯直接新增,也可以通過Nacos直接配置,下圖是通過Nacos配置的,如下圖:

Nacos推送成功後將會在sentinel控制檯看到這條流控規則的配置,如下圖:

OK,至此叢集流控到這兒就介紹完了,配置好之後可以自己試一下效果,陳某就不再演示了。

官方文件:https://github.com/alibaba/Sentinel/wiki/叢集流控

17、閘道器限流如何配置?

這一塊內容在後續介紹到閘道器的時候會詳細講,這裡就不再細說了,有想要了解的可以看官方文件。

官方文件:https://github.com/alibaba/Sentinel/wiki/閘道器限流

18、整合openFeign如何實現熔斷降級?

這個在上篇openFeign的文章中有詳細介紹:openFeign奪命連環9問,這誰受得了?陳某這裡就不再重複介紹了,有不知道的可以看上面這篇文章。

最後說一句

陳某碼字不易,這篇文章寫了兩週,如果覺得不錯,點贊轉發在看收藏支援一下,謝謝!

以上原始碼已經上傳GitHub,需要的公眾號【碼猿技術專欄】回覆關鍵詞9528獲取。

相關文章