繼續上一章未完成的sentinel;
直接實操;
關于測試:本文使用線程池線程異步執行模擬并發結合Mock框架測試
?其他文章
服務容錯治理框架resilience4j&sentinel基礎應用---微服務的限流/熔斷/降級解決方案-CSDN博客
conda管理python環境-CSDN博客
快速搭建對象存儲服務 - Minio,并解決臨時地址暴露ip、短鏈接請求改變瀏覽器地址等問題-CSDN博客
大模型LLMs的MCP入門-CSDN博客
使用LangGraph構建多代理Agent、RAG-CSDN博客
大模型LLMs框架Langchain之鏈詳解_langchain.llms.base.llm詳解-CSDN博客
大模型LLMs基于Langchain+FAISS+Ollama/Deepseek/Qwen/OpenAI的RAG檢索方法以及優化_faiss ollamaembeddings-CSDN博客
大模型LLM基于PEFT的LoRA微調詳細步驟---第二篇:環境及其詳細流程篇-CSDN博客
大模型LLM基于PEFT的LoRA微調詳細步驟---第一篇:模型下載篇_vocab.json merges.txt資源文件下載-CSDN博客?使用docker-compose安裝Redis的主從+哨兵模式_使用docker部署redis 一主一從一哨兵模式 csdn-CSDN博客
docker-compose安裝canal并利用rabbitmq同步多個mysql數據_docker-compose canal-CSDN博客
目錄
Step1、引入依賴
Step2、啟動dashboard控制臺
Step3、配置application.yml
Demo1、限流FlowRule---自定義局部異常攔截
controller
測試
Demo2、限流FlowRule---使用自定義統一處理異常類
controller
定義:處理異常類UnifiedSentinelHandler.java
測試
Demo3、限流FlowRule---自定義全局異常處理類
controller
定義全局的統一處理類-低級:GlobalExceptionHandler.java
定義全局的統一處理類-高級:SentinelExceptionHandler.java
測試
Demo4、熔斷DegradeRule---自定義全局異常
controller
測試
Demo5、授權AuthorityRule ---?自定義全局異常
controller
測試
Demo6、熱點參數ParamFlowRule-自定義全局異常
controller
測試
Step1、引入依賴
<!-- 版本控制 --> <spring-boot.version>3.4.1</spring-boot.version><spring-cloud.version>2024.0.0</spring-cloud.version><spring-cloud-alibaba.version>2023.0.3.2</spring-cloud-alibaba.version>
<!-- 父項目依賴 --> <!-- spring boot 依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!-- spring cloud 依賴 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!-- spring cloud alibaba 依賴 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!-- 子項目依賴 --><!-- SpringCloud Alibaba Sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><!-- 引入Springboot-web SpringMVC --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
Step2、啟動dashboard控制臺
需要去官網下載“sentinel-dashboard-1.8.8.jar”版本自行選擇...
登錄WebUI:http://localhost:8080/#/login
密碼賬號:sentinel/sentinel? ? --- 默認的
啟動命令:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar###CMD可能解析錯誤,所以用“""”轉義
java "-Dserver.port=8080" "-Dcsp.sentinel.dashboard.server=localhost:8080" "-Dproject.name=sentinel-dashboard" -jar .\sentinel-dashboard-1.8.8.jar在項目中配置:spring.cloud.sentinel.transport.dashboard=localhost:8080,就可以將項目綁定在sentinel-dashboard中;
Step3、配置application.yml
spring:profiles:active: @profiles.active@### sentinel流量控制配置sentinel:.... 其他比如日志等配置略...eager: true # 取消控制臺懶加載 即是否立即加載 Sentinel 規則transport:# 控制臺地址 ... 目的是將這個注冊到這個控制臺,而不是在這個項目中打開控制臺;# 如果要使用控制臺,需要單獨開啟“sentinel-dashboard-1.8.8.jar”dashboard: 127.0.0.1:8080
# filter:
# enabled: false # 關閉 Web 層的自動資源名稱生成web-context-unify: false # 打開調用鏈路
Demo1、限流FlowRule---自定義局部異常攔截
controller
@Slf4j
@RestController
@RequestMapping("/sentinel")
public class SentinelTestController {
/****************************************************************************************************************************//*** 案例一:自定義局部異常攔截:* blockHandler:Sentinel 流量控制或熔斷、降級觸發時執行的回調方法;方法參數、返回值類型要和partSentinel()方法一致;* fallback:業務邏輯拋出異常時才會執行;方法參數、返回值類型要和partSentinel()方法一致..*/@GetMapping("/part/{time}/{flag}")@SentinelResource(value = "part_sentinel", blockHandler = "partHandleBlock", fallback = "partFallbackMethod")public String partSentinel(@PathVariable("time") Long time, @PathVariable("flag") String flag) throws InterruptedException {if ("1".equals(flag)) {throw new NullPointerException("拋出異常...");}log.info("partSentinel 休眠...{}s", time);Thread.sleep(time * 1000);log.info("partSentinel 休眠結束...");return "partSentinel success";}private String partHandleBlock(Long time, String flag, BlockException ex) {return "熔斷降級: " + ex.getClass().getSimpleName();}private String partFallbackMethod(Long time, String flag, Throwable ex) {return "方法執行異常: " + ex.getMessage();}/*** 如果使用了nacos那么要失效...* 使用這個只需要在瀏覽器連續訪問即可*/@PostConstructprivate void partSentinelInitFlowRules() {FlowRule rule = new FlowRule();rule.setResource("part_sentinel");// FLOW_GRADE_QPS:基于QPS(每秒請求數)進行流量控制// FLOW_GRADE_THREAD:基于并發線程數進行流量控制rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);rule.setCount(1); // 每秒最多允許 1 個請求List<FlowRule> rules = new ArrayList<>();rules.add(rule);FlowRuleManager.loadRules(rules);}
}
測試
使用CompletableFuture.runAsync異步無返回值+for循環+線程池+MockMvc形式測試
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.hamcrest.Matchers.containsString;@SpringBootTest
@AutoConfigureMockMvc
public class SentinelTestControllerMockTest {@Autowiredprivate MockMvc mockMvc;static ThreadPoolExecutor threadPoolExecutor;static {/*** 5個核心線程、最多10個線程、5秒的線程空閑存活時間、能容納100個任務的阻塞隊列以及當任務無法添加到線程池時使用的策略;* 對于CPU密集型任務,你可能希望將核心線程數設置為與處理器數量相匹配;而對于IO密集型任務,則可以設置更高的線程數以提高并發度。*/threadPoolExecutor = new ThreadPoolExecutor(5,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy());}/*** 測試流量控制(QPS = 1)* 連續發送兩個請求,第二個會被限流* CompletableFuture.runAsync無返回值*/@Testvoid testQpsFlowControlRunAsync() throws Exception {List<CompletableFuture<Void>> futures = new ArrayList<>();for (int i = 0; i < 3; i++) {final int index = i + 1;CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {String response = mockMvc.perform(get("/sentinel/part/1/0")).andReturn().getResponse().getContentAsString();System.out.println("請求[" + index + "] 成功: " + response);} catch (Exception e) {System.err.println("請求[" + index + "] 異常: " + e.getMessage());throw new RuntimeException("請求失敗: " + e.getMessage(), e);}}, threadPoolExecutor);
// future.join(); // 等待異步線程執行完畢// 添加到列表用于后續統一處理futures.add(future);}// 等待所有請求完成CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));// 可選:添加最終聚合操作allFutures.thenRun(() -> System.out.println("? 所有異步請求已完成"));// 阻塞主線程直到全部完成(測試用)allFutures.join();}/*** 測試業務異常觸發 fallback*/@Testvoid testBusinessException() throws Exception {
// mockMvc.perform(get("/sentinel/part/1/1"))
// .andDo(MockMvcResultHandlers.print()) // 打印詳細信息
// .andExpect(status().isOk());System.out.println(mockMvc.perform(get("/sentinel/part/1/1")) // 打印返回值.andReturn().getResponse().getContentAsString());
// mockMvc.perform(get("/sentinel/part/0/1"))
// .andExpect(content().string("方法執行異常: 拋出異常..."));}
}
Demo2、限流FlowRule---使用自定義統一處理異常類
controller
/*** 案例二:使用自定義統一處理異常類* 和案例一類似,方法參數、返回值保持一致;** @param time* @param flag* @return* @throws InterruptedException*/@GetMapping("/unified/{time}/{flag}")@SentinelResource(value = "unified_sentinel",blockHandlerClass = UnifiedSentinelHandler.class,blockHandler = "handleBlock1", // 指定熔斷方法fallbackClass = UnifiedSentinelHandler.class,fallback = "fallbackMethod1")// 指定回調方public String unifiedSentinel(@PathVariable("time") Integer time, @PathVariable("flag") String flag) throws InterruptedException {if ("1".equals(flag)) {throw new NullPointerException("拋出異常...");}log.info("unifiedSentinel 休眠...{}s", time);Thread.sleep(time * 1000);log.info("unifiedSentinel 休眠結束...");return "unifiedSentinel success";}@PostConstructprivate void unifiedSentinelInitFlowRules() {List<FlowRule> rules = new ArrayList<>();FlowRule rule = new FlowRule();rule.setResource("unified_sentinel");// FLOW_GRADE_QPS:基于QPS(每秒請求數)進行流量控制// FLOW_GRADE_THREAD:基于并發線程數進行流量控制
// rule.setGrade(RuleConstant.FLOW_GRADE_QPS);rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);rule.setCount(1);rules.add(rule);FlowRuleManager.loadRules(rules);}
定義:處理異常類UnifiedSentinelHandler.java
報錯/限流熔斷降級都會走這個類的返回值
public class UnifiedSentinelHandler {public static String handleBlock1(Integer time, String flag, BlockException ex) {/*** 限流了,會被拋出“FlowException”異常*/return "SentinelHandler handleBlock1: " + ex.getClass().getSimpleName();}public static String fallbackMethod1(Long time, String flag, Throwable ex) {return "SentinelHandler fallbackMethod1: " + ex.getMessage();}
}
測試
/*** 案例二:使用自定義統一處理異常類:* 直接使用線程池測試** @throws Exception*/@Testvoid testDemo2QpsFlowControlThreadPool() throws Exception {// 第一個請求應該成功,后續會被限流/降級for (int i = 0; i < 3; i++) {threadPoolExecutor.execute(() -> {try {System.out.println(mockMvc.perform(get("/sentinel/unified/1/0")).andReturn().getResponse().getContentAsString());} catch (Exception e) {e.printStackTrace(); // 明確打印異常}});}// 使用Thread.sleep(2000);}/*** 案例二:使用自定義統一處理異常類:* 測試業務異常觸發 fallback*/@Testvoid testDemo2BusinessException() throws Exception {System.out.println(mockMvc.perform(get("/sentinel/unified/1/1")).andReturn().getResponse().getContentAsString());}
Demo3、限流FlowRule---自定義全局異常處理類
controller
/*** 案例三:自定義全局異常處理類;* 結合@RestControllerAdvice處理即“GlobalExceptionHandler/SentinelExceptionHandler.java”類* Throwable ---> Exception ---> BlockException ---> FlowException/DegradeException/ParamFlowException/AuthorityException* 在異常處理時,系統會依次捕獲:所以不能同時將Throwable/Exception和FlowException/DegradeException/ParamFlowException/AuthorityException設置在同一個類中;* 所以如果要使用通用的異常處理類,并且要攔截Exception/Throwable* 可以將BlockException系列異常和Exception/Throwable分開,并利用@Order注解設置優先級;* 此處定義兩個類:GlobalExceptionHandler(處理全局)、SentinelExceptionHandler(處理Sentinel相關異常)* 測試,限流:FlowException.class類* @param time* @param flag* @return* @throws InterruptedException*/@GetMapping("/globel/flow/{time}/{flag}")@SentinelResource(value = "globel_flow_sentinel")public String globleFlowSentinel(@PathVariable("time") Integer time, @PathVariable("flag") String flag) throws InterruptedException {if ("1".equals(flag)) {throw new RuntimeException("拋出異常...");}log.info("globleFlowSentinel 休眠...{}s", time);Thread.sleep(time * 1000);log.info("globleFlowSentinel 休眠結束...");return "globleFlowSentinel success";}@PostConstructprivate void globleFlowSentinelInitFlowRules() {List<FlowRule> rules = new ArrayList<>();FlowRule rule = new FlowRule();rule.setResource("globel_flow_sentinel");
// rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);rule.setGrade(RuleConstant.FLOW_GRADE_QPS);rule.setCount(1); // 每秒最多允許 1 個請求rules.add(rule);FlowRuleManager.loadRules(rules);
// FlowRule{
// resource=globel_flow_sentinel,
// limitApp=default,
// grade=0, // 0 表示線程數模式
// count=1.0, // 限制并發線程數為 1
// strategy=0, // 直接模式
// controlBehavior=0, // 直接拒絕
// warmUpPeriodSec=10, // 預熱時間 ----- 有這個,所以啟動項目以后,不能直接訪問,要等幾秒,限流才生效.
// maxQueueingTimeMs=500 // 最大排隊時間
// }// 打印加載的規則log.info("Loaded flow rules: {}", FlowRuleManager.getRules());}
定義全局的統一處理類-低級:GlobalExceptionHandler.java
@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE) // 最低優先級
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public Map<String, Object> handleException(Exception e) {log.error("捕獲到Exception: ", e);return new HashMap<>() {{put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());put("msg", "服務器內部錯誤");}};}@ExceptionHandler(Throwable.class)public Map<String, Object> handleThrowable(Throwable e) {log.error("捕獲到Throwable: ", e);return new HashMap<>() {{put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());put("msg", "系統異常");}};}
}
定義全局的統一處理類-高級:SentinelExceptionHandler.java
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高優先級
@Slf4j
public class SentinelExceptionHandler {@ExceptionHandler(FlowException.class)public Map<String, Object> handlerFlowException() {log.info("FlowException");return new HashMap<>() {{put("code", HttpStatus.TOO_MANY_REQUESTS.value());put("msg", "被限流");}};}@ExceptionHandler(DegradeException.class)public Map<String, Object> handlerDegradeException() {log.info("DegradeException");return new HashMap<>() {{put("code", HttpStatus.TOO_MANY_REQUESTS.value());put("msg", "被熔斷");}};}@ExceptionHandler(ParamFlowException.class)public Map<String, Object> handlerParamFlowException() {log.info("ParamFlowException");return new HashMap<>() {{put("code", HttpStatus.TOO_MANY_REQUESTS.value());put("msg", "熱點限流");}};}@ExceptionHandler(AuthorityException.class)public Map<String, Object> handlerAuthorityException() {log.info("AuthorityException");return new HashMap<>() {{put("code", HttpStatus.UNAUTHORIZED.value());put("msg", "暫無權限");}};}@ExceptionHandler(BlockException.class)public Map<String, Object> handleBlockException(BlockException e) {log.info("BlockException: {}", e.getClass().getSimpleName());return new HashMap<>() {{put("code", HttpStatus.TOO_MANY_REQUESTS.value());put("msg", "訪問被限制");}};}
}
測試
/*** 案例三:自定義全局異常處理類:* 直接使用線程池測試;* 限流返回:SentinelExceptionHandler里面的handlerFlowException方法“{"msg":"被限流","code":429}”** @throws Exception*/@Testvoid testDemo3QpsFlowControlThreadPool() throws Exception {// 第一個請求應該成功,后續會被限流/降級List<CompletableFuture<Void>> futures = new ArrayList<>();for (int i = 0; i < 10; i++) {final int index = i + 1;CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {String response = mockMvc.perform(get("/sentinel/globel/flow/5/0")).andReturn().getResponse().getContentAsString();System.out.println("請求[" + index + "] 成功: " + response);} catch (Exception e) {System.err.println("請求[" + index + "] 異常: " + e.getMessage());throw new RuntimeException("請求失敗: " + e.getMessage(), e);}}, threadPoolExecutor);
// future.join(); // 等待異步線程執行完畢// 添加到列表用于后續統一處理futures.add(future);}// 等待所有請求完成CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));// 可選:添加最終聚合操作allFutures.thenRun(() -> System.out.println("? 所有異步請求已完成"));// 阻塞主線程直到全部完成(測試用)allFutures.join();}/*** 案例三:限流 --- 自定義全局異常處理類:* 測試業務異常觸發 fallback;* 報錯返回:GlobalExceptionHandler里面的handleException方法“{"msg":"服務器內部錯誤","code":500}”;* 如果沒有設置Exception異常捕獲那么會被handleFallback方法拋出“{"msg":"系統異常","code":500}”*/@Testvoid testDemo3FlowException() throws Exception {System.out.println(mockMvc.perform(get("/sentinel/globel/flow/1/1")).andReturn().getResponse().getContentAsString());}
Demo4、熔斷DegradeRule---自定義全局異常
注意:完整的統一處理異常的類在demo3;
自定義全局;結合@RestControllerAdvice處理即“CustomExceptionHandler.java”類 * 測試熔斷降級:DegradeException.class類
controller
/*** 案例四:自定義全局;結合@RestControllerAdvice處理即“CustomExceptionHandler.java”類* 測試熔斷降級:DegradeException.class類* <p>* 配置熔斷規則方法一:在 Sentinel 控制臺中,為資源 globelClass 添加降級規則,設置慢調用比例閾值(如響應時間超過 500ms 的比例超過 50%)* 配置熔斷規則方法二:初始化DegradeRule** 在sentinel中不支持超時;只能由客戶端控制該請求的時間,服務端無法控制;* - 比如:在本案例中系統會阻塞在"Thread.sleep(time * 1000)(工作中,可能是第三方服務、redis、MySQL網絡等原因造成)",* - 如果每個請求都要阻塞了50s,那么只有在第三個請求時才會自動熔斷降級;* - 這樣一來只能等1、2請求完畢也就是100s以后才會熔斷降級* 官方目前好像沒有自動超時熔斷的方法;* 解決方法一:所以我們可以使用CompletableFuture.supplyAsync異步請求,并設置超時返回值:* // 使用異步任務執行核心邏輯* CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {* try {* if ("1".equals(flag)) {* throw new RuntimeException("拋出異常...");* }* log.info("globleDegradeSentinel 休眠...{}s", time);* Thread.sleep(time * 1000);* log.info("globleDeggradeSentinel 休眠結束...");* return "globleDegradeSentinel success";* } catch (InterruptedException e) {* Thread.currentThread().interrupt();* throw new RuntimeException("任務被中斷", e);* }* });** // 設置超時時間(例如 3 秒)* try {* return future.get(3, TimeUnit.SECONDS);* } catch (Exception e) {* future.cancel(true); // 中斷任務* log.warn("接口超時,已中斷");* return "請求超時,請稍后重試";* }* 其他解決方法: ---- 不過這個是這個方法調用第三方接口的---對于本例的sleep方法不適用。* server.servlet.session.timeout=3* server.tomcat.connection-timeout=3000* spring.mvc.async.request-timeout=3000 # 設置SpringMVC的超時時間為5s*/@GetMapping("/globel/degrade/{time}/{flag}")@SentinelResource(value = "globel_degrade_sentinel")public String globleDegradeSentinel(@PathVariable("time") Integer time, @PathVariable("flag") String flag) throws InterruptedException {if ("1".equals(flag)) {throw new RuntimeException("拋出異常...");}log.info("globleDegradeSentinel 休眠...{}s", time);Thread.sleep(time * 1000);log.info("globleDegradeSentinel 休眠結束...");return "globleDegradeSentinel success";}/*** 期望:在30s以內,有2個請求,接口響應時間超過 1000ms 時觸發熔斷,2s后恢復* 30s內請求2次:http://127.0.0.1:8077/weixin/sentinel/globel/degrade/5/10 被限制;哪怕是休眠時間結束了也會被限制;* 配置在nacos中的* {* "resource": "globel_degrade_sentinel",* "grade": 0,* "count": 1000,* "slowRatioThreshold": 0.1,* "minRequestAmount": 2,* "timeWindow": 2,* "statIntervalMs": 30000* }*/@PostConstructprivate void globleDegradeSentinelInitDegradeRules() {DegradeRule rule = new DegradeRule();rule.setResource("globel_degrade_sentinel");rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // // 基于響應時間的熔斷rule.setCount(1000); // 響應時間超過 1000msrule.setTimeWindow(2); // 熔斷時間窗口為 2 秒rule.setMinRequestAmount(2); // 默認值,統計窗口內的最小請求數rule.setStatIntervalMs(30000); // 默認值,統計窗口長度為 30000msDegradeRuleManager.loadRules(Collections.singletonList(rule));// 打印加載的規則log.info("Loaded degrade rules: {}", DegradeRuleManager.getRules());}
測試
/*** 案例四:熔斷 --- 自定義全局異常處理類* 測試業務異常觸發 熔斷;* 正常情況,1、2會正常請求;3會熔斷;4正常請求;5熔斷;* 報錯返回:CustomExceptionHandler/SentinelExceptionHandler里面的handlerDegradeException方法“{"msg":"被熔斷","code":429}”;*/@Testvoid testDemo4DegradeException() throws Exception {for (int i = 1; i < 6; i++) {LocalTime now = LocalTime.now();System.out.printf("當前時間:%02d:%02d | 請求次數:%02d | 返回值:%s%n",now.getMinute(),now.getSecond(),i,mockMvc.perform(get("/sentinel/globel/degrade/10/0")).andReturn().getResponse().getContentAsString());Thread.sleep(1000);}}
Demo5、授權AuthorityRule ---?自定義全局異常
controller
/*** 案例五:自定義全局;結合@RestControllerAdvice處理即“CustomExceptionHandler/GlobalExceptionHandler/SentinelExceptionHandler”類* 測試授權:AuthorityException.class類* <p>* 方法一:在 Sentinel 控制臺中,為資源 globelClass 添加授權規則,設置黑名單或白名單。* 方法二:初始化AuthorityRule** @param time* @param flag* @return* @throws InterruptedException*/@GetMapping("/globel/auth/{time}/{flag}")@SentinelResource(value = "globel_auth_sentinel")public String globleAuthSentinel(@PathVariable("time") Integer time, @PathVariable("flag") String flag, HttpServletRequest request) throws InterruptedException {if ("1".equals(flag)) {throw new RuntimeException("拋出異常...");}
// if ("2".equals(flag)) {
// globleAuthSentinelInitAuthRules(getClientIpAddress(request));
// }System.out.println(getClientIpAddress(request));log.info("globleAuthSentinel 休眠...{}s", time);Thread.sleep(time * 1000);log.info("globleAuthSentinel 休眠結束...");return "globleAuthSentinel success";}private String getClientIpAddress(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}// 如果經過多個代理,X-Forwarded-For 可能包含多個 IP,取第一個if (ip != null && ip.contains(",")) {ip = ip.split(",")[0].trim();}return ip;}/*** 如果我們使用Nacos管理配置,就可以配置如下內容* 配置在nacos中的* [* {* "resource": "globel_auth_sentinel",* "strategy": 1,* "limitApp": "192.168.3.58,192.168.3.49"* }* ]*/@PostConstructprivate void globleAuthSentinelInitAuthRules() {
// globleAuthSentinelInitAuthRules("192.168.3.58");globleAuthSentinelInitAuthRules("可以設置為手機IP");}private void globleAuthSentinelInitAuthRules(String ip) {AuthorityRule rule = new AuthorityRule();rule.setResource("globel_auth_sentinel");
// rule.setStrategy(RuleConstant.AUTHORITY_WHITE); // 白名單rule.setStrategy(RuleConstant.AUTHORITY_BLACK); // 黑名單rule.setLimitApp(ip); // 限制特定 IPAuthorityRuleManager.loadRules(Collections.singletonList(rule));// 打印加載的規則log.info("Loaded auth rules: {}", AuthorityRuleManager.getRules());}
測試
關于測試和其他測試有所不同,在配置的時候可以使用手機來測試;
step1、設置“rule.setStrategy(RuleConstant.AUTHORITY_BLACK); // 黑名單”,并設置ip為“127.0.0.1” --- 此步限制本地IP連接---主要是為了使用手機能連接進來;
step2、查看開發電腦的IP,使用CMD命令控制臺:ipconfig(window使用命令)
step3、找到“無線局域網適配器 WLAN:"--->“IPv4 地址 . . . . . . . . . . . . : xxxx”;
step4、用手機請求地址:“xxx/globel/auth/3/2” ---- 這一步主要是為了利用“System.out.println(getClientIpAddress(request));”獲取手機ip
step5、在rule.setLimitApp(ip)設置IP,將其設置為手機IP;
最后效果:
設置rule.setStrategy=RuleConstant.AUTHORITY_BLACK?ip=127.0.0.1;手機可訪問,電腦訪問不了;
設置rule.setStrategy=RuleConstant.AUTHORITY_BLACK?ip=手機IP;其余設備可訪問,手機訪問不了;
設置rule.setStrategy=RuleConstant.AUTHORITY_WHITE?ip=127.0.0.1;電腦可訪問,其余設備訪問不了;
設置rule.setStrategy=RuleConstant.AUTHORITY_WHITE?ip=手機IP;其余設備不可訪問,手機可以訪問;
Demo6、熱點參數ParamFlowRule-自定義全局異常
controller
/*** 案例六:自定義全局;結合@RestControllerAdvice處理即“CustomExceptionHandler.java”類* 測試熱點參數:ParamFlowException.class類* <p>* 方法一:在 Sentinel 控制臺中,為資源 globelClass 添加熱點參數規則,設置特定參數值的 QPS 閾值。* 方法二:初始化AuthorityRule*/@GetMapping("/globel/paramflow/{time}/{flag}")@SentinelResource(value = "globel_param_flow_sentinel")public String globleParamFlowSentinel(@PathVariable("time") Integer time, @PathVariable("flag") String flag) throws InterruptedException {String threadName = Thread.currentThread().getName();Long threadId = Thread.currentThread().getId();System.out.printf("threadName: %s | threadId: %d \n", threadName, threadId);if ("1".equals(flag)) {throw new NullPointerException("拋出異常...");}log.info("globleParamFlowSentinel 休眠...{}s", time);Thread.sleep(time * 1000);log.info("globleParamFlowSentinel 休眠結束...");return "globleParamFlowSentinel success";}/*** 對參數索引為“1”下標位置的參數進行限制,即對參數flag進行限制;* 期望一:限流模式為線程數模式,即在20s內,攜帶flag參數的請求超過3次(http://127.0.0.1:8077/sentinel/globel/paramflow/10/0),第4次將被限流;其他地址請求到第6次限流* 期望二:對 flag=10 時,請求到第二次進行限流;當“http://127.0.0.1:8077/weixin/sentinel/globel/paramflow/10/10”請求到第2次時被限流* 期望三:對 flag=測試 時,請求到第三次進行限流;即“http://127.0.0.1:8077/sentinel/globel/paramflow/10/測試”請求到第3次時限流*/@PostConstructprivate void globleParamFlowSentinelInitParamFlowRules() {// 當“http://127.0.0.1:8077/sentinel/globel/paramflow/8/2”請求到第4次時被限流ParamFlowRule rule = new ParamFlowRule("globel_param_flow_sentinel").setParamIdx(1) // 參數索引(下標)(flag 參數) ;對應 SphU.entry(xxx, args) 中的參數索引位置
// .setGrade(RuleConstant.FLOW_GRADE_THREAD) // 線程限流模式 .setGrade(RuleConstant.FLOW_GRADE_QPS) // QPS限流模式 .setDurationInSec(20) // 統計窗口時間 默認1s.setControlBehavior(0) // 流控制效果 勻速排隊失敗/快速失敗(默認).setMaxQueueingTimeMs(0) // 最大排隊等待時間 ,僅在勻速排隊模式生效.setCount(3); // 限流閾值ParamFlowItem item1 = new ParamFlowItem();item1.setCount(1); // 閾值item1.setObject("10"); // 參數值 ---- 對“.setParamIdx()”位置的參數的值進行限制; 本例是對“flag”的值進行限制;item1.setClassType(String.class.getName()); // 參數類型ParamFlowItem item2 = new ParamFlowItem();item2.setCount(2); // 閾值item2.setObject("測試"); // 參數值 --- 當參數為“測試”時,請求到第二次被限流item2.setClassType(String.class.getName()); // 參數類型List<ParamFlowItem> rules = new ArrayList<>();rules.add(item1);rules.add(item2);rule.setParamFlowItemList(rules);ParamFlowRuleManager.loadRules(Collections.singletonList(rule));// 打印加載的規則log.info("Loaded ParamFlow rules: {}", ParamFlowRuleManager.getRules());}
測試
可以使用網頁直接訪問路徑“/globel/paramflow/10/0”、“/globel/paramflow/10/10”、“/globel/paramflow/10/測試”;即可
使用Junit測試時
/*** 案例六:熱點參數* @throws Exception*/@Testvoid testDemo6ParamFlowException() throws Exception {// 第一個請求應該成功,后續會被限流/降級List<CompletableFuture<Void>> futures = new ArrayList<>();for (int i = 0; i < 10; i++) {final int index = i + 1;CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {try {String response = mockMvc.perform(get("/sentinel/globel/paramflow/8/2")).andReturn().getResponse().getContentAsString();System.out.println("請求[" + index + "] 成功,攜帶參數flag,第四次將被熱點限流: " + response);} catch (Exception e) {System.err.println("請求[" + index + "] 異常: " + e.getMessage());throw new RuntimeException("請求失敗: " + e.getMessage(), e);}}, threadPoolExecutor);CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {try {String response = mockMvc.perform(get("/sentinel/globel/paramflow/10/10")).andReturn().getResponse().getContentAsString();System.out.println("請求[" + index + "] 成功,攜帶參數flag=10,第2次將被熱點限流: " + response);} catch (Exception e) {System.err.println("請求[" + index + "] 異常: " + e.getMessage());throw new RuntimeException("請求失敗: " + e.getMessage(), e);}}, threadPoolExecutor);CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {try {String response = mockMvc.perform(get("/sentinel/globel/paramflow/10/測試")).andReturn().getResponse().getContentAsString();System.out.println("請求[" + index + "] 成功,攜帶參數flag=測試,第3次將被熱點限流: " + response);} catch (Exception e) {System.err.println("請求[" + index + "] 異常: " + e.getMessage());throw new RuntimeException("請求失敗: " + e.getMessage(), e);}}, threadPoolExecutor);
// future.join(); // 等待異步線程執行完畢// 添加到列表用于后續統一處理futures.add(future1);futures.add(future2);futures.add(future3);}// 等待所有請求完成CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));// 可選:添加最終聚合操作allFutures.thenRun(() -> System.out.println("? 所有異步請求已完成"));// 阻塞主線程直到全部完成(測試用)allFutures.join();}