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
的文章。
🤗 您的點贊
和轉發
是對我最大的支持。
📢 掃碼關注 哈嘍沃德先生
「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~