1.Sentinel控制臺的安裝
下載地址:?
Releases · alibaba/Sentinelx
下載后是一個jar包
進入目錄
CMD命令
java -jar?"sentinel-dashboard-1.8.8 .jar"
?如果發生了端口沖突則使用以下命令啟動 修改端口號為8090
java ?-Dserver.port=8090 ?-jar "sentinel-dashboard-1.8.8 .jar"
啟動成功:
訪問控制臺
Sentinel Dashboard
賬號密碼默認都是sentinel
進入首頁:
此時控制臺已經安裝完成
2.整合微服務
sentinel 的 starter 依賴:
將此sentinel依賴引入項目中
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
yml配置:
spring:cloud:sentinel:transport:# 添加sentinel的控制臺地址dashboard: 127.0.0.1:8090
如果是mvc的接口無需 @SentinelResource. 如果是業務方法, 需要 @SentinelResource
@SentinelResource("hello")public String hello() {return "Hello Sentinel";}
@SentinelResource 注解用來標識資源是否被限流、降級。上述例子上該注解的屬性 hello 表示資源名。 @SentinelResource 還提供了其它額外的屬性如 blockHandler,blockHandlerClass,fallback 用于表示限流或降級的操作,更多內容可以參考 Sentinel 注解支持。
啟用feign對sentinel的支持:
#啟用feign對sentinel的支持
feign:sentinel:enabled: true # 啟用 Sentinel 對 Feign 的支持
此時我們只需要訪問集成服務中的任意一個路徑就能在sentinel中查看到
2.1 sentinel限流(blockHandler) 講解
2.1.1沒有使用@SentinelResource注解(使用默認限流響應)
創建一個類用于測試限流
SentinelRatilimitConteroller
@RestController
@RequestMapping("/order/sentinelRatilimitConteroller")
public class SentinelRatilimitConteroller {/*** 測試sentinel限流 (沒有添加@)* @return*/@GetMapping("/testSentinelRatilimitA")public String testSentinelRatilimitA() {return "沒有達到限流的規則 --- 正常訪問";}}
此時我們啟動服務 訪問地址
localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitA
能夠正常訪問
進入sentinel的控制臺,給當前路徑添加限流規則
當QPS超過1時的請求會快速失敗返回默認的失敗響應
此時限流返回到響應為默認的響應
2.1.2使用@SentinelResource注解自定義限流響應
寫法1:
在controller中添加以下代碼
/*** 測試sentinel限流 (添加了@SentinelResource)* @SentinelResource value 為 限流規則的資源名稱 blockHandler 為限流規則的異常處理方法*/@GetMapping("/testSentinelRatilimitB")@SentinelResource(value = "testSentinelRatilimitB",blockHandler = "testSentinelRatilimitBHandleException")public String testSentinelRatilimitB() {return "沒有達到限流的規則 --- 添加了@SentinelResource 正常訪問";}/***作為(testSentinelRatilimitB)的限流異常返回方法*/public String testSentinelRatilimitBHandleException(BlockException e) {return "限流了 --- 添加了@SentinelResource 異常處理";}
* @SentinelResource value 為 限流規則的資源名稱 blockHandler 為限流規則的異常處理方法
? ? ?使得方法testSentinelRatilimitB 觸發限流時 返回?testSentinelRatilimitBHandleException方法
正常訪問:localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitB
添加限流規則:
?
快速訪問:
執行了自定義限流處理
對應關系圖
寫法2:(推薦)
如果需要獨立設置一個 限流類還可以這樣寫
添加一個新的controller
指定了blockHandler類中的指定方法作為限流處理
@GetMapping("/testSentinelRatilimitC")@SentinelResource(value = "testSentinelRatilimitC",blockHandlerClass = OrderBlockHandler.class,// 限流異常返回方法所在類blockHandler = "testSentinelRatilimitC") //指定限流異常返回方法public String testSentinelRatilimitC() {return "沒有達到限流的規則 --- 添加了@SentinelResource 默認異常處理";}
訪問localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitC
為他編寫一個類 和方法 (方法必須被 pblic static 修飾)
public class OrderBlockHandler {// 必須是public static方法// 參數和返回類型要與原方法一致,最后加一個BlockException參數public static String testSentinelRatilimitC(BlockException ex) {return "觸發限流了";}}
設置限流規則
快速訪問:
2.2異常處理(fallback)
為一個接口配置?@SentinelResource(value = "testSentinelRatilimitD",
fallback = "testSentinelRatilimitDfallback")
fallback屬性表示當前方法遇到異常會調用指定的方法
@GetMapping("/testSentinelRatilimitD/{id}")@SentinelResource(value = "testSentinelRatilimitD",blockHandler = "testSentinelRatilimitDBlockHandler",fallback = "testSentinelRatilimitDfallback")public String testSentinelRatilimitD(@PathVariable("id") Integer id) {if (id == 0){throw new RuntimeException("id 不能為0");}return "沒有達到限流的規則 --- id為"+ id;}//作為限流 處理public String testSentinelRatilimitDBlockHandler(@PathVariable("id") Integer id,BlockException e) {return "已觸發限流 --- id為"+ id;}//作為 熔斷 降級 異常處理public String testSentinelRatilimitDfallback(@PathVariable("id") Integer id,Throwable e) {return "觸發率異常處理,傳入id為:"+ id;}
2.2.1測試限流
訪問地址:localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitD/1
為他設置限流規則:
多次訪問:
觸發限流規則,走了BlockHandler 所配置的方法
2.2.2測試異常
訪問
http://localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitD/0
傳入id值為0,我們設置id為0時會拋出異常,看看是否會走fallback所配置的方法
成功觸發fallback
3.熱點參數限流
在我們比如商城服務中的商品流量是不均勻的,我們可能要對同一個接口的不同參數進行限流
例如
在查詢某個商品信息時,同樣根據商品id進行查詢商品,商品ID: 1:華為 2:小米 3:蘋果
查詢接口
queryPhone(int id);
傳入id為1的量較大,那么我們就需要對id為1的商品進行限流,如圖:
寫一個方法:
/*** 熱點參數限流*/@GetMapping("/testSentinelRatilimitE/{id}")@SentinelResource(value = "testSentinelRatilimitE",blockHandler = "testSentinelRatilimitEBlockHandler")public String testSentinelRatilimitE(@PathVariable("id") int id){return "沒有觸發限流傳入id為:"+ id;}/*** 熱點參數限流處理*/public String testSentinelRatilimitEBlockHandler(int id,BlockException ex){return "觸發限流處理,傳入id為:"+ id;}
訪問 :localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitE/0
參數索引: 寫0時則為接口中的第一個參數
索引為0
統計窗口:表示*秒內統計的量
表示這個接口 帶上參數ID時 QPS達到1開始限流
訪問接口:localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitE/0
觸發了限流
參數例外項:
例如當我們傳入id為1時,我們的限流規則變成QPS為3才進行限流
上面的參數表示,當帶參數索引為0時,限流QPS為1 ,參數例外項:當參數索引0 傳入值為1 時,限流閾值QPS提升為3才限流
務必要點擊添加按鈕
測試發現id等于1時 ,只有QPS達到3才會觸發限流
4.sentinel規則持久化(不會因為微服務重啟丟失配置)
我們服務配置完成后,我們的微服務重啟會導致我們這個服務的所有的配置丟失,不可能每重啟一次我們就重新配置一次限流規則,所以需要持久化,持久化到哪里呢? 我們會選擇持久化到配置中心 nacos中?
在服務中引入新的Maven坐標
<!--sentinel持久化--> <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId> </dependency>
修改yml配置
配置ds1 ds2 ds3 分別代表(流控規則)(熔斷降級規則) (熱點參數規則)的持久化配置
我們以流控規則作為演示
打開nacos創建對應的配置文件
內容:
[{"resource": "testSentinelRatilimitE","limitApp": "default","grade": 1,"count": 1,"strategy": 0,"controlBehavior": 0,"clusterMode": false}
]
// [
// {
// // 🔹【資源名稱】你想保護的是哪個接口或功能?
// // 這里是:testSentinelRatilimitE
// // 代碼中要用 SphU.entry("testSentinelRatilimitE") 來標記這個資源
// "resource": "testSentinelRatilimitE",// // 🔹【限流對象】對誰限流?
// // "default" 表示:所有人都一樣,一視同仁
// // 你也可以寫成 "appA"、"appB",實現不同應用不同策略
// "limitApp": "default",// // 🔹【限流方式】按什么標準限流?
// // 1 = 按每秒請求數(QPS)來限
// // 0 = 按并發線程數來限(不常用)
// "grade": 1,// // 🔹【限流閾值】最多允許多少流量?
// // 這里是:每秒最多 1 個請求
// // 超過第 1 個就直接拒絕,不讓進
// "count": 1,// // 🔹【限流策略】怎么判斷要不要限?
// // 0 = 直接對自己限流(最簡單常用)
// // 1 = 當另一個資源被限流時,我也跟著限(關聯限流)
// // 2 = 只對某個調用鏈路限流(比如 A 調 B 才限)
// "strategy": 0,// // 🔹【觸發限流后怎么辦?】
// // 0 = 立刻拒絕,拋出“被限流”錯誤(快速失敗)
// // 1 = 先預熱,慢慢放行(適合突發流量)
// // 2 = 排隊等待,一個一個處理(削峰填谷)
// "controlBehavior": 0,// // 🔹【是單機還是集群?】
// // false = 每臺機器獨立限流(單機模式,適合大多數場景)
// // true = 多臺機器共享一個總限流值(需要額外配置集群)
// "clusterMode": false
// }
// ]
!!!!非常非常難用,需要自己手動寫json用于配置持久化,Sentinel控制臺配置成為擺設,因為控制臺上寫的配置不會持久化,必須自己寫!!!!
同樣是阿里巴巴的產品 Nacos比sentinel好用太多了!
重啟 當前服務
訪問localhost:8082/order/sentinelRatilimitConteroller/testSentinelRatilimitE/1
nacos 配置限流規則成功
來到sentinel控制臺查看限流規則
成功從nacos中讀取限流規則
5.Sentinel集成OpenFeign實現fallback熔斷降級
往公共API模塊中引入sentinel maven 依賴
<!--sentinel--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
在api接口中添加fallbackFactory屬性并創建對應的類
創建
StockApiFallback.class
@Component
public class StockApiFallback implements FallbackFactory<stockApi> {@Overridepublic stockApi create(Throwable cause) {return new stockApi() {@Overridepublic ResultData<String> reduceInventory() {throw new RPCException("201","調用庫存服務調用失敗");}@Overridepublic Integer reduceStock() {throw new RPCException("201","調用庫存服務調用失敗");}};}}
在fallback 的時候為了保證fallback后全局事務還能正常的回滾,fallback一般拋出自定義異常讓事務回滾,并且全局異常處理器 處理整形后才返回給用戶
自定義異常類:
@Data
public class RPCException extends RuntimeException{private String code;private String message;private Object data;public RPCException(String code,String message,Object data) {super(message);this.message = message;this.code = code;this.data =data ;}public RPCException(String code,String message) {super(message);this.message = message;this.code = code;}}
自定義異常捕獲
@RestControllerAdvice
@Slf4j
public class RPCExceptionHandler {// 攔截自定義業務異常@ExceptionHandler(RPCException.class)public ResultData handleBizException(RPCException e) {log.warn("遠程調用異常: code={}, msg={}", e.getCode(), e.getMessage());return ResultData.fail(e.getCode(),"系統出現錯誤,請稍后再試"); // 通常返回 200,業務碼區分}}
重點:!!!!? ?然后在調用者模塊 配置文件中開啟OpenFeign 支持Sentinel
feign:sentinel:enabled: true # 啟用 Sentinel 對 Feign 的支持
,然后重新啟動 測試全局事務回滾是否正常
發現系統出現異常后,使用的是全局異常處理器返回的消息
并且,事務全部正確回滾
任務完成
6.Sentinel結合網關Gateway(限流熔斷,貌似有BUG希望得到指點)
在Gateway網關服務中添加Maven依賴
<!--Gateway 結合 Sentinel做限流--> <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency> <!--javax--> <dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version><scope>compile</scope> </dependency>
創建Gateway配置類
package com.xiaog.config;import com.alibaba.csp.sentinel.Tracer;
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 com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
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.client.WebClientResponseException;
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 GatewaySentinelConfiguration {private final List<ViewResolver> viewResolvers;private final ServerCodecConfigurer serverCodecConfigurer;public GatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer) {this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);this.serverCodecConfigurer = serverCodecConfigurer;}@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {// Register the block exception handler for Spring Cloud Gateway.return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);}@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public GlobalFilter sentinelGatewayFilter() {return new SentinelGatewayFilter();}/*** ========================================分割線======================================= 以上的代碼不需要改動*//*** Spring 容器初始化的時候執行該方法*/@PostConstructpublic void doInit() {// 加載網關限流規則 initGatewayRules(); // 加載網關限流規則和熔斷規則// 加載自定義限流異常處理器(處理+自定義返回信息的內容,類似觸發流控規則保護)initBlockHandler(); //觸發限流和熔斷時會調用自定義的限流異常處理器}/*** 網關限流規則* 建議直接在 Sentinel 控制臺上配置*/private void initGatewayRules() {Set<GatewayFlowRule> rules = new HashSet<>();/*resource:資源名稱,可以是網關中的 route 名稱或者用戶自定義的 API 分組名稱count:限流閾值intervalSec:統計時間窗口,單位是秒,默認是 1 秒*/rules.add(new GatewayFlowRule("order-service") //限流id 在yml中配置的id Gatewa.routes.id.setCount(3) // 限流閾值.setIntervalSec(3)); // 統計時間窗口,單位是秒,默認是 1 秒rules.add(new GatewayFlowRule("stock-service").setCount(5) // 限流閾值.setIntervalSec(3)); // 統計時間窗口,單位是秒,默認是 1 秒// --------------------限流分組-----------end-----------// 加載網關限流規則GatewayRuleManager.loadRules(rules);// 加載限流分組
// initCustomizedApis();// ---------------熔斷-降級配置-------------DegradeRule degradeRule = new DegradeRule("order-service") // 資源名稱.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) // 異常比率模式(秒級).setCount(0.5) // 異常比率閾值(50%).setTimeWindow(10) // 熔斷降級時間(10s).setMinRequestAmount(2);//最小請求數為2DegradeRule degradeRule2 = new DegradeRule("stock-service") //調用這個服務 的時候 異常比例過高則會觸發熔斷.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) // 異常比率模式(秒級).setCount(0.5) // 異常比率閾值(50%).setTimeWindow(10).setMinRequestAmount(2);// 加載規則.DegradeRuleManager.loadRules(Arrays.asList(degradeRule,degradeRule2));// 打印當前所有規則System.out.println("=== 當前生效的熔斷規則 ===");DegradeRuleManager.getRules().forEach(System.out::println);// DegradeRuleManager.loadRules(Collections.singletonList(degradeRule));}/*** 自定義限流異常處理器(當服務被限流或者熔斷時都會執行這個方法)*/private void initBlockHandler() {BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {@Overridepublic Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {Map<String, String> result = new HashMap<>(3);result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase()+"系統繁忙");if (throwable instanceof DegradeException) {result.put("中文信息", "服務熔斷,請稍后重試");} else if (throwable instanceof FlowException ||throwable instanceof ParamFlowException ||throwable instanceof SystemBlockException) {result.put("中文信息", "請求過多,請稍后重試");} else {// 兜底邏輯result.put("中文信息", "系統限流或熔斷,請稍后重試");}return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON)//.body(BodyInserters.fromValue(result));.body(BodyInserters.fromObject(result));}};// 加載自定義限流異常處理器 GatewayCallbackManager.setBlockHandler(blockRequestHandler);}}
當前存在熔斷BUG 當服務異常請求很多時 沒辦法進行熔斷 依舊可以繼續訪問
有懂的通信麻煩幫忙找一下bug解決一下博主的疑惑
!!!!這個注解務必使用?
package javax.annotation;下的
然后重啟測試限流
限流成功