雙劍合璧 Nacos 結合 Sentinel 實現流量安全控制


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.RELEASESpring 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 處理邏輯。若未配置 blockHandlerfallbackdefaultFallback,則被限流降級時會將 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 使用率達到閾值即觸發系統保護。

動態規則擴展

官網文檔:

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 進行限流。

官網文檔:

創建項目

創建 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 定制異常提示信息。

自定義異常提示

GatewayCallbackManagersetBlockHandler 注冊函數用於實現自定義的邏輯,處理被限流的請求。

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 的文章。

🤗 您的點贊轉發是對我最大的支持。

📢 掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕松噢 ~


免責聲明!

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



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