
Alibaba Sentinel 是一款高性能且輕量級的流量控制、熔斷降級解決方案。是面向分布式服務架構的高可用流量控制組件。
Sentinel 官網:https://sentinelguard.io/zh-cn/
Github:https://github.com/alibaba/Sentinel
Sentinel 是什么
隨着微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 主要以流量為切入點,從流量控制、熔斷降級、系統自適應保護等多個維度來保障微服務的穩定性。
Sentinel 具有以下特征:
-
「豐富的應用場景」:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。 -
「完備的實時監控」:Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應用的單台機器秒級數據,甚至 500 台以下規模的集群的匯總運行情況。 -
「廣泛的開源生態」:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。 -
「完善的 SPI 擴展點」:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定制邏輯。例如定制規則管理、適配動態數據源等。
「Sentinel 主要特征」

「Sentinel 開源生態」

Sentinel 目前已經針對 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等進行了適配,用戶只需引入相應依賴並進行簡單配置即可非常方便地享受 Sentinel 的高可用流量防護能力。Sentinel 還為 Service Mesh 提供了集群流量防護的能力。未來 Sentinel 還會對更多常用框架進行適配。
Sentinel 分為兩個部分:
-
核心庫(Java 客戶端)不依賴任何框架/庫,能夠運行於所有 Java 運行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支持。 -
控制台(Dashboard)基於 Spring Boot 開發,打包后可以直接運行,不需要額外的 Tomcat 等應用容器。
Sentinel 的歷史
-
2012 年,Sentinel 誕生,主要功能為入口流量控制。 -
2013-2017 年,Sentinel 在阿里巴巴集團內部迅速發展,成為基礎技術模塊,覆蓋了所有的核心場景。Sentinel 也因此積累了大量的流量歸整場景以及生產實踐。 -
2018 年,Sentinel 開源,並持續演進。 -
2019 年,Sentinel 朝着多語言擴展的方向不斷探索,推出 C++ 原生版本,同時針對 Service Mesh 場景也推出了 Envoy 集群流量控制支持,以解決 Service Mesh 架構下多語言限流的問題。 -
2020 年,推出 Sentinel Go 版本,繼續朝着雲原生方向演進。
Sentinel 核心
Sentinel 的使用可以分為兩個部分:
-
核心庫(Java 客戶端):不依賴任何框架/庫,能夠運行於 Java 7 及以上的版本的運行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支持(見 主流框架適配)。 -
控制台(Dashboard):控制台主要負責管理推送規則、監控、集群限流分配管理、機器發現等。
Sentinel 控制台
Sentinel 提供一個輕量級的開源控制台,它提供機器發現以及健康情況管理、監控(單機和集群),規則管理和推送的功能。
官網文檔:https://github.com/alibaba/Sentinel/wiki/控制台
獲取控制台
您可以從 release 頁面 下載最新版本的控制台 jar 包。
您也可以從最新版本的源碼自行構建 Sentinel 控制台:
-
下載 控制台 工程 -
使用以下命令將代碼打包成一個 fat jar: mvn clean package
啟動控制台
啟動命令如下,本文使用的是目前最新 1.7.2 版本:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.2.jar
❝「注意」:啟動 Sentinel 控制台需要 JDK 版本為 1.8 及以上版本。
❞
其中 -Dserver.port=8080
用於指定 Sentinel 控制台端口為 8080
。
從 Sentinel 1.6.0 起,Sentinel 控制台引入基本的「登錄」功能,默認用戶名和密碼都是 sentinel
。可以參考 鑒權模塊文檔 配置用戶名和密碼。
❝注:若您的應用為 Spring Boot 或 Spring Cloud 應用,您可以通過 Spring 配置文件來指定配置,詳情請參考 Spring Cloud Alibaba Sentinel 文檔。
❞
為了方便啟動,可以編寫一個啟動腳本 run.bat
:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.2.jar
pause
訪問
訪問:http://localhost:8080/

輸入默認用戶名和密碼 sentinel
點擊登錄。至此控制台就安裝完成了。

環境准備
sentinel-demo
聚合工程。SpringBoot 2.3.0.RELEASE
、Spring Cloud Hoxton.SR4
。
-
Nacos 注冊中心
-
product-service
:商品服務,提供了/product/{id}
接口 -
order-service-rest
:訂單服務,基於Ribbon
通過RestTemplate
調用商品服務 -
order-server-feign
:訂單服務,基於Feign
通過聲明式服務調用商品服務

客戶端接入控制台
控制台啟動后,客戶端需要按照以下步驟接入到控制台:
-
添加依賴 -
定義資源 -
定義規則
先把可能需要保護的資源定義好,之后再配置規則。也可以理解為,只要有了資源,我們就可以在任何時候靈活地定義各種流量控制規則。在編碼的時候,只需要考慮這個代碼是否需要保護,如果需要保護,就將之定義為一個資源。
由於我們的項目是 Spring Cloud 項目,所以可以借助官方文檔來進行學習。
Spring 官網文檔:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
Github 文檔:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
添加依賴
父工程需要添加如下依賴:
<dependencyManagement>
<dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
子工程需要添加如下依賴:
<!-- spring cloud alibaba sentinel 依賴 -->
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
配置文件
客戶端需要啟動 Transport 模塊來與 Sentinel 控制台進行通信。
order-service-rest
的 application.yml
spring:
cloud: # 配置 Sentinel sentinel: transport: port: 8719 dashboard: localhost:8080
這里的 spring.cloud.sentinel.transport.port
端口配置會在應用對應的機器上啟動一個 Http Server,該 Server 會與 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一個限流規則,會把規則數據 push 給這個 Http Server 接收,Http Server 再將規則注冊到 Sentinel 中。
初始化客戶端
「確保客戶端有訪問量」,Sentinel 會在「客戶端首次調用的時候」進行初始化,開始向控制台發送心跳包。
簡單的理解就是:訪問一次客戶端,Sentinel 即可完成客戶端初始化操作,並持續向控制台發送心跳包。
訪問
多次訪問:http://localhost:9090/order/1 然后查看控制台實時監控
結果如下:

定義資源
「資源」 是 Sentinel 中的核心概念之一。我們說的資源,可以是任何東西,服務,服務里的方法,甚至是一段代碼。最常用的資源是我們代碼中的 Java 方法。Sentinel 提供了 @SentinelResource
注解用於定義資源,並提供了 AspectJ 的擴展用於自動定義資源、處理 BlockException
等。
只要通過 Sentinel API 定義的代碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作為資源名來標示資源。
官網文檔:https://github.com/alibaba/Sentinel/wiki/如何使用#定義資源
注解支持
官網文檔:https://github.com/alibaba/Sentinel/wiki/注解支持
OrderServiceImpl.java
package com.example.service.impl;
import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.example.pojo.Order; import com.example.service.OrderService; import com.example.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Arrays; @Service public class OrderServiceImpl implements OrderService { @Autowired private ProductService productService; /** * 根據主鍵和訂單編號查詢訂單 * * @param id * @param orderNo * @return */ @Override @SentinelResource(value = "selectOrderByIdAndOrderNo", blockHandler = "selectOrderByIdAndOrderNoBlockHandler", fallback = "selectOrderByIdAndOrderNoFallback") public Order selectOrderByIdAndOrderNo(Integer id, String orderNo) { return new Order(id, orderNo, "中國", 2666D, Arrays.asList(productService.selectProductById(1))); } // 服務流量控制處理,參數最后多一個 BlockException,其余與原函數一致。 public Order selectOrderByIdAndOrderNoBlockHandler(Integer id, String orderNo, BlockException ex) { // Do some log here. ex.printStackTrace(); return new Order(id, "服務流量控制處理-托底數據", "中國", 2666D, Arrays.asList(productService.selectProductById(1))); } // 服務熔斷降級處理,函數簽名與原函數一致或加一個 Throwable 類型的參數 public Order selectOrderByIdAndOrderNoFallback(Integer id, String orderNo, Throwable throwable) { System.out.println("order-service 服務的 selectOrderById 方法出現異常,異常信息如下:" + throwable); return new Order(id, "服務熔斷降級處理-托底數據", "中國", 2666D, Arrays.asList(productService.selectProductById(1))); } }
ProductServiceImpl.java
package com.example.service.impl;
import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.example.pojo.Product; import com.example.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /** * 商品管理 */ @Service public class ProductServiceImpl implements ProductService { @Autowired private RestTemplate restTemplate; /** * 根據主鍵查詢商品 * * @param id * @return */ @SentinelResource(value = "selectProductById", blockHandler = "selectProductByIdBlockHandler", fallback = "selectProductByIdFallback") @Override public Product selectProductById(Integer id) { return restTemplate.getForObject("http://product-service/product/" + id, Product.class); } // 服務流量控制處理,參數最后多一個 BlockException,其余與原函數一致。 public Product selectProductByIdBlockHandler(Integer id, BlockException ex) { // Do some log here. ex.printStackTrace(); return new Product(id, "服務流量控制處理-托底數據", 1, 2666D); } // 服務熔斷降級處理,函數簽名與原函數一致或加一個 Throwable 類型的參數 public Product selectProductByIdFallback(Integer id, Throwable throwable) { System.out.println("product-service 服務的 selectProductById 方法出現異常,異常信息如下:" + throwable); return new Product(id, "服務熔斷降級處理-托底數據", 1, 2666D); } }
❝注意:注解方式埋點不支持 private 方法。
❞
@SentinelResource
用於定義資源,並提供可選的異常處理和 fallback 配置項。 @SentinelResource
注解包含以下屬性:
-
value
:資源名稱,必需項(不能為空) -
entryType
:entry 類型,可選項(默認為EntryType.OUT
) -
blockHandler
/blockHandlerClass
:blockHandler
對應處理BlockException
的函數名稱,可選項。blockHandler 函數訪問范圍需要是public
,返回類型需要與原方法相匹配,參數類型需要和原方法相匹配並且最后加一個額外的參數,類型為BlockException
。blockHandler 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定blockHandlerClass
為對應的類的Class
對象,注意對應的函數必需為 static 函數,否則無法解析。 -
fallback
: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.6.0 之前的版本 fallback 函數只針對降級異常(
❞DegradeException
)進行處理,「不能針對業務異常進行處理」。
特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException
時只會進入 blockHandler
處理邏輯。若未配置 blockHandler
、fallback
和 defaultFallback
,則被限流降級時會將 BlockException
「直接拋出」(若方法本身未定義 throws BlockException 則會被 JVM 包裝一層 UndeclaredThrowableException
)。
從 1.4.0 版本開始,注解方式定義資源支持自動統計業務異常,無需手動調用 Tracer.trace(ex)
來記錄業務異常。Sentinel 1.4.0 以前的版本需要自行調用 Tracer.trace(ex)
來記錄業務異常。
定義規則
Sentinel 的所有規則都可以在「內存態中動態地查詢及修改,修改之后立即生效」。同時 Sentinel 也提供相關 API,供您來定制自己的規則策略。
Sentinel 支持以下幾種規則:「流量控制規則」、「熔斷降級規則」、「熱點參數規則」、「系統保護規則」和「來源訪問控制規則」。
官網文檔:https://github.com/alibaba/Sentinel/wiki/如何使用#規則的種類
流量控制規則
添加流量控制規則
選擇 簇點鏈路
找到定義好的資源 selectProductById
並點擊對應的規則按鈕進行設置。

比如我們設置一個流量控制規則,定義資源訪問的 QPS 為 1(每秒能處理查詢數目)。


測試
快速刷新頁面多次訪問:http://localhost:9090/order/idAndOrderNo?id=1&orderNo=order-001 結果如下:

熔斷降級規則
模擬服務出錯
修改 order-service-rest
項目中的核心代碼,模擬服務出錯。
package com.example.service.impl;
import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.example.pojo.Product; import com.example.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /** * 商品管理 */ @Service public class ProductServiceImpl implements ProductService { @Autowired private RestTemplate restTemplate; /** * 根據主鍵查詢商品 * * @param id * @return */ @SentinelResource(value = "selectProductById", blockHandler = "selectProductByIdBlockHandler", fallback = "selectProductByIdFallback") @Override public Product selectProductById(Integer id, String productName) { // 模擬查詢主鍵為 1 的商品信息會導致異常 if (1 == id) throw new RuntimeException("查詢主鍵為 1 的商品信息導致異常"); return restTemplate.getForObject("http://product-service/product/" + id, Product.class); } // 服務流量控制處理,參數最后多一個 BlockException,其余與原函數一致。 public Product selectProductByIdBlockHandler(Integer id, BlockException ex) { // Do some log here. ex.printStackTrace(); return new Product(id, "服務流量控制處理-托底數據", 1, 2666D); } // 服務熔斷降級處理,函數簽名與原函數一致或加一個 Throwable 類型的參數 public Product selectProductByIdFallback(Integer id, Throwable throwable) { System.out.println("product-service 服務的 selectProductById 方法出現異常,異常信息如下:" + throwable); return new Product(id, "服務熔斷降級處理-托底數據", 1, 2666D); } }
添加熔斷降級規則
熔斷降級規則支持相應時間、異常比例、異常數三種方式。

測試
訪問:http://localhost:9090/order/idAndOrderNo?id=1&orderNo=order-001 結果如下:

熱點參數規則
熱點參數規則是一種更細粒度的流控規則,它允許將規則具體到參數上。比如 selectOrderByIdAndOrderNo
方法有兩個參數,我們對第一個參數進行限流,對第二個參數不限流。
添加熱點參數規則
選擇 簇點鏈路
找到定義好的資源 selectOrderByIdAndOrderNo
並點擊對應的規則按鈕進行設置。

設置熱點參數規則,定義對資源的第一個參數的 QPS 為 1(每秒能處理查詢數目)。


測試
分別用兩個參數訪問,會發現只對第一個參數限流了。
快速刷新頁面多次訪問:http://localhost:9090/order/idAndOrderNo?id=1 被限流。

快速刷新頁面多次訪問:http://localhost:9090/order/idAndOrderNo?orderNo=order-001 正常訪問。

授權規則
很多時候,我們需要根據調用來源來判斷該次請求是否被允許,這時候可以使用 Sentinel 的來源訪問控制的功能。來源訪問控制根據資源的請求來源(origin)限制資源是否通過。
Sentinel 提供了 RequestOriginParser
接口來處理來源。一旦 Sentinel 保護的接口資源被訪問,Sentinel 就會調用 RequestOriginParser
的實現類去解析訪問來源。
自定義來源處理規則
package com.example.sentinel;
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * 自定義來源處理規則 */ @Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { return request.getParameter("userName"); } }
新增授權規則
下圖配置的意思是資源 selectOrderByIdAndOrderNo
只有 userName=zhangsan
的用戶無法訪問(黑名單)

測試
快速刷新頁面多次訪問:http://localhost:9090/order/idAndOrderNo?id=1&userName=zhangsan 被限流。

快速刷新頁面多次訪問:http://localhost:9090/order/idAndOrderNo?id=1&userName=lisi 正常訪問。

系統保護規則
系統保護規則是從應用級別的入口流量進行控制,從單台機器的總體 LOAD、RT、線程數、入口 QPS 和 CPU 使用率五個維度監控應用數據,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
系統保護規則是應用整體維度的,而不是資源維度的,並且僅對入口流量(進入應用的流量)生效。
-
Load(僅對 Linux/Unix-like 機器生效):當系統 load 超過閾值,且系統當前的並發線程數超過系統容量時才會觸發系統保護。系統容量由系統的 maxQps * minRt 計算得出。設定參考值一般是 CPU cores * 2.5。 -
RT:當單台機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。 -
線程數:當單台機器上所有入口流量的並發線程數達到閾值即觸發系統保護。 -
入口 QPS:當單台機器上所有入口流量的 QPS 達到閾值即觸發系統保護。 -
CPU使用率:當單台機器上所有入口流量的 CPU 使用率達到閾值即觸發系統保護。
動態規則擴展
官網文檔:
-
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#動態數據源支持 -
https://github.com/alibaba/Sentinel/wiki/動態規則擴展#示例
SentinelProperties
內部提供了 TreeMap
類型的 datasource
屬性用於配置數據源信息。支持:
-
文件配置規則 -
Nacos 配置規則 -
ZooKeeper 配置規則 -
Apollo 配置規則 -
Redis 配置規則
文件配置規則
Sentinel 支持通過本地文件加載規則配置,使用方式如下(限流規則作為演示):
spring:
cloud: # 配置 Sentinel sentinel: datasource: ds1: file: file: classpath:flowRule.json data-type: json rule-type: flow
flowRule.json 對應 com.alibaba.csp.sentinel.slots.block.RuleConstant
各屬性。
[
{ "resource": "selectProductList", "count": 1, "grade": 1, "limitApp": "default", "strategy": 0, "controlBehavior": 0 } ]
重要屬性:
Field | 說明 | 默認值 |
---|---|---|
resource | 資源名,資源名是限流規則的作用對象 | |
count | 限流閾值 | |
grade | 限流閾值類型,QPS 模式(1)或並發線程數模式(0) | QPS 模式 |
limitApp | 流控針對的調用來源 | default ,代表不區分調用來源 |
strategy | 調用關系限流策略:直接、鏈路、關聯 | 根據資源本身(直接) |
controlBehavior | 流控效果(直接拒絕 / 排隊等待 / 慢啟動模式),不支持按調用關系限流 | 直接拒絕 |
clusterMode | 是否集群限流 | 否 |
訪問客戶端以后,刷新控制台,查看流控規則如下:

RestTemplate 支持
Spring Cloud Alibaba Sentinel 支持對 RestTemplate 調用的服務進行服務保護。需要在構造 RestTemplate Bean 時添加 @SentinelRestTemplate
注解。
啟動類
OrderServiceRestApplication.java
package com.example;
import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.example.exception.ExceptionUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class OrderServiceRestApplication { @Bean @LoadBalanced @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class, fallback = "fallback", fallbackClass = ExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(OrderServiceRestApplication.class, args); } }
服務熔斷處理類
ExceptionUtil.java 必須使用靜態方法。
package com.example.exception;
import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.fastjson.JSON; import com.example.pojo.Product; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpResponse; public class ExceptionUtil { // 服務流量控制處理 public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) { exception.printStackTrace(); return new SentinelClientHttpResponse( JSON.toJSONString(new Product(1, "服務流量控制處理-托底數據", 1, 2666D))); } // 服務熔斷降級處理 public static ClientHttpResponse fallback(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) { exception.printStackTrace(); return new SentinelClientHttpResponse( JSON.toJSONString(new Product(1, "服務熔斷降級處理-托底數據", 1, 2666D))); } }
訪問
控制台設置流量控制規則,定義資源訪問的 QPS 為 1(每秒能處理查詢數目)。
快速刷新頁面多次訪問:http://localhost:9090/order/1 結果如下:

OpenFeign 支持
添加依賴
<!-- spring cloud alibaba sentinel 依賴 -->
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- spring cloud openfeign 依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
開啟 Sentinel
server:
port: 9091 # 端口 spring: application: name: order-service-feign # 應用名稱 cloud: # 配置 Nacos 注冊中心 nacos: discovery: enabled: true # 如果不想使用 Nacos 進行服務注冊和發現,設置為 false 即可 server-addr: 127.0.0.1:8848 # Nacos 服務器地址 # 配置 Sentinel sentinel: transport: port: 8719 dashboard: localhost:8080 # feign 開啟 sentinel 支持 feign: sentinel: enabled: true
熔斷降級
ProductServiceFallback.java
package com.example.fallback;
import com.example.pojo.Product; import com.example.service.ProductService; import feign.hystrix.FallbackFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * 服務熔斷降級處理可以捕獲異常 */ @Slf4j @Component public class ProductServiceFallbackFactory implements FallbackFactory<ProductService> { @Override public ProductService create(Throwable throwable) { return new ProductService() { @Override public Product selectProductById(Integer id) { // 獲取日志,在需要捕獲異常的方法中進行處理 log.error("product-service 服務的 selectProductById 方法出現異常,異常信息如下:" + throwable); return new Product(id, "托底數據", 1, 2666D); } }; } }
消費服務
ProductService.java
package com.example.service;
import com.example.fallback.ProductServiceFallbackFactory; import com.example.pojo.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; // 聲明需要調用的服務 @FeignClient(value = "product-service", fallbackFactory = ProductServiceFallbackFactory.class) public interface ProductService { /** * 根據主鍵查詢商品 * * @param id * @return */ @GetMapping("/product/{id}") Product selectProductById(@PathVariable("id") Integer id); }
OrderServiceImpl.java
package com.example.service.impl;
import com.example.pojo.Order; import com.example.service.OrderService; import com.example.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Arrays; @Service public class OrderServiceImpl implements OrderService { @Autowired private ProductService productService; /** * 根據主鍵查詢訂單 * * @param id * @return */ @Override public Order selectOrderById(Integer id) { return new Order(id, "order-001", "中國", 2666D, Arrays.asList(productService.selectProductById(1))); } }
控制層
package com.example.controller;
import com.example.pojo.Order; import com.example.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; /** * 根據主鍵查詢訂單 * * @param id * @return */ @GetMapping("/{id}") public Order selectOrderById(@PathVariable("id") Integer id) { return orderService.selectOrderById(id); } }
啟動類
package com.example;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; // 開啟 FeignClients 注解 @EnableFeignClients // 開啟 @EnableDiscoveryClient 注解,當前版本默認會開啟該注解 //@EnableDiscoveryClient @SpringBootApplication public class OrderServiceFeignApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceFeignApplication.class, args); } }
測試
控制台信息如下:

添加流量控制規則,定義資源訪問的 QPS 為 1(每秒能處理查詢數目)。

快速刷新頁面多次訪問:http://localhost:9091/order/1 結果如下:

或者關閉服務提供者,訪問:http://localhost:9091/order/1 結果如下:

Gateway 支持
Sentinel 支持對 Spring Cloud Gateway、Netflix Zuul 等主流的 API Gateway 進行限流。

官網文檔:
-
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel -
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81#spring-cloud-gateway
創建項目
創建 gateway-server-sentinel
項目。
添加依賴
單獨使用添加 sentinel-spring-cloud-gateway-adapter
依賴即可。
若想跟 Sentinel Starter 配合使用,需要加上 spring-cloud-alibaba-sentinel-gateway
依賴來讓 spring-cloud-alibaba-sentinel-gateway
模塊里的 Spring Cloud Gateway 自動化配置類生效。
同時請將 spring.cloud.sentinel.filter.enabled
配置項置為 false(若在網關流控控制台上看到了 URL 資源,就是此配置項沒有置為 false)。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <!-- 繼承父依賴 --> <parent> <artifactId>gateway-demo</artifactId> <groupId>com.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>gateway-server-sentinel</artifactId> <!-- 項目依賴 --> <dependencies> <!-- spring cloud gateway 依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- spring cloud alibaba nacos discovery 依賴 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- 單獨使用 --> <!-- sentinel gateway adapter 依賴 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency> <!-- 和 Sentinel Starter 配合使用 --> <!-- <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> --> </dependencies> </project>
配置文件
server:
port: 9001 # 端口 spring: application: name: gateway-server-sentinel # 應用名稱 cloud: sentinel: filter: enabled: false gateway: discovery: locator: # 是否與服務發現組件進行結合,通過 serviceId 轉發到具體服務實例。 enabled: true # 是否開啟基於服務發現的路由規則 lower-case-service-id: true # 是否將服務名稱轉小寫 # 路由規則 routes: - id: order-service # 路由 ID,唯一 uri: lb://order-service # 目標 URI,lb:// 根據服務名稱從注冊中心獲取服務請求地址 predicates: # 斷言(判斷條件) # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之后 - Path=/order/**
限流規則配置類
使用時只需注入對應的 SentinelGatewayFilter
實例以及 SentinelGatewayBlockExceptionHandler
實例即可。
GatewayConfiguration.java
package com.example.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 限流規則配置類 */ @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; /** * 構造器 * * @param viewResolversProvider * @param serverCodecConfigurer */ public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * 限流異常處理器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * 限流過濾器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /** * Spring 容器初始化的時候執行該方法 */ @PostConstruct public void doInit() { // 加載網關限流規則 initGatewayRules(); } /** * 網關限流規則 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); /* resource:資源名稱,可以是網關中的 route 名稱或者用戶自定義的 API 分組名稱 count:限流閾值 intervalSec:統計時間窗口,單位是秒,默認是 1 秒 */ rules.add(new GatewayFlowRule("order-service") .setCount(3) // 限流閾值 .setIntervalSec(60)); // 統計時間窗口,單位是秒,默認是 1 秒 // 加載網關限流規則 GatewayRuleManager.loadRules(rules); } }
啟動類
GatewayServerSentinelApplication.java
package com.example;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; // 開啟 EurekaClient 注解,目前版本如果配置了 Eureka 注冊中心,默認會開啟該注解 //@EnableEurekaClient @SpringBootApplication public class GatewayServerSentinelApplication { public static void main(String[] args) { SpringApplication.run(GatewayServerSentinelApplication.class, args); } }
訪問
多次訪問:http://localhost:9001/order/1 結果如下:

接口 BlockRequestHandler
的默認實現為 DefaultBlockRequestHandler
,當觸發限流時會返回默認的錯誤信息:Blocked by Sentinel: FlowException
。我們可以通過 GatewayCallbackManager
定制異常提示信息。
自定義異常提示
GatewayCallbackManager
的 setBlockHandler
注冊函數用於實現自定義的邏輯,處理被限流的請求。
package com.example.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.util.*; /** * 限流規則配置類 */ @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; /** * 構造器 * * @param viewResolversProvider * @param serverCodecConfigurer */ public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * 限流異常處理器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * 限流過濾器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /** * Spring 容器初始化的時候執行該方法 */ @PostConstruct public void doInit() { // 加載網關限流規則 initGatewayRules(); // 加載自定義限流異常處理器 initBlockHandler(); } /** * 網關限流規則 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); /* resource:資源名稱,可以是網關中的 route 名稱或者用戶自定義的 API 分組名稱 count:限流閾值 intervalSec:統計時間窗口,單位是秒,默認是 1 秒 */ rules.add(new GatewayFlowRule("order-service") .setCount(3) // 限流閾值 .setIntervalSec(60)); // 統計時間窗口,單位是秒,默認是 1 秒 // 加載網關限流規則 GatewayRuleManager.loadRules(rules); } /** * 自定義限流異常處理器 */ private void initBlockHandler() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map<String, String> result = new HashMap<>(); result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value())); result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()); result.put("route", "order-service"); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(result)); } }; // 加載自定義限流異常處理器 GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
「訪問」
多次訪問:http://localhost:9001/order/1 結果如下:

分組限流
package com.example.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.util.*; /** * 限流規則配置類 */ @Configuration public class GatewayConfiguration { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; /** * 構造器 * * @param viewResolversProvider * @param serverCodecConfigurer */ public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * 限流異常處理器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * 限流過濾器 * * @return */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } /** * Spring 容器初始化的時候執行該方法 */ @PostConstruct public void doInit() { // 加載網關限流規則 initGatewayRules(); // 加載自定義限流異常處理器 initBlockHandler(); } /** * 網關限流規則 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); /* resource:資源名稱,可以是網關中的 route 名稱或者用戶自定義的 API 分組名稱 count:限流閾值 intervalSec:統計時間窗口,單位是秒,默認是 1 秒 */ // rules.add(new GatewayFlowRule("order-service") // .setCount(3) // 限流閾值 // .setIntervalSec(60)); // 統計時間窗口,單位是秒,默認是 1 秒 // --------------------限流分組----------start---------- rules.add(new GatewayFlowRule("product-api") .setCount(3) // 限流閾值 .setIntervalSec(60)); // 統計時間窗口,單位是秒,默認是 1 秒 rules.add(new GatewayFlowRule("order-api") .setCount(5) // 限流閾值 .setIntervalSec(60)); // 統計時間窗口,單位是秒,默認是 1 秒 // --------------------限流分組-----------end----------- // 加載網關限流規則 GatewayRuleManager.loadRules(rules); // 加載限流分組 initCustomizedApis(); } /** * 自定義限流異常處理器 */ private void initBlockHandler() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map<String, String> result = new HashMap<>(); result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value())); result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()); result.put("route", "order-service"); return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(result)); } }; // 加載自定義限流異常處理器 GatewayCallbackManager.setBlockHandler(blockRequestHandler); } /** * 限流分組 */ private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); // product-api 組 ApiDefinition api1 = new ApiDefinition("product-api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // 匹配 /product-service/product 以及其子路徑的所有請求 add(new ApiPathPredicateItem().setPattern("/product-service/product/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); // order-api 組 ApiDefinition api2 = new ApiDefinition("order-api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // 只匹配 /order-service/order/index add(new ApiPathPredicateItem().setPattern("/order-service/order/index")); }}); definitions.add(api1); definitions.add(api2); // 加載限流分組 GatewayApiDefinitionManager.loadApiDefinitions(definitions); } }
「訪問」
訪問:http://localhost:9001/product-service/product/1 「觸發限流」
訪問:http://localhost:9001/order-service/order/index 「觸發限流」
訪問:http://localhost:9001/order-service/order/1 「不會觸發限流」
至此 Sentinel 服務哨兵知識點就講解結束了。

本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議
。
大家可以通過 分類
查看更多關於 Spring Cloud
的文章。
🤗 您的點贊
和轉發
是對我最大的支持。
📢 掃碼關注 哈嘍沃德先生
「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕松噢 ~

