继续上一章未完成的sentinel;
直接实操;
关于测试:本文使用线程池线程异步执行模拟并发结合Mock框架测试
其他文章
服务容错治理框架resilience4j&sentinel基础应用---微服务的限流/熔断/降级解决方案-CSDN博客
快速搭建对象存储服务 - Minio,并解决临时地址暴露ip、短链接请求改变浏览器地址等问题-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博客
目录
Demo2、限流FlowRule---使用自定义统一处理异常类
定义:处理异常类UnifiedSentinelHandler.java
定义全局的统一处理类-低级:GlobalExceptionHandler.java
定义全局的统一处理类-高级:SentinelExceptionHandler.java
Demo5、授权AuthorityRule --- 自定义全局异常
Demo6、热点参数ParamFlowRule-自定义全局异常
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那么要失效...
* 使用这个只需要在浏览器连续访问即可
*/
@PostConstruct
private 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 {
@Autowired
private 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无返回值
*/
@Test
void 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
*/
@Test
void 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";
}
@PostConstruct
private 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
*/
@Test
void 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
*/
@Test
void 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";
}
@PostConstruct
private 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
*/
@Test
void 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}”
*/
@Test
void 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
* }
*/
@PostConstruct
private void globleDegradeSentinelInitDegradeRules() {
DegradeRule rule = new DegradeRule();
rule.setResource("globel_degrade_sentinel");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // // 基于响应时间的熔断
rule.setCount(1000); // 响应时间超过 1000ms
rule.setTimeWindow(2); // 熔断时间窗口为 2 秒
rule.setMinRequestAmount(2); // 默认值,统计窗口内的最小请求数
rule.setStatIntervalMs(30000); // 默认值,统计窗口长度为 30000ms
DegradeRuleManager.loadRules(Collections.singletonList(rule));
// 打印加载的规则
log.info("Loaded degrade rules: {}", DegradeRuleManager.getRules());
}
测试
/**
* 案例四:熔断 --- 自定义全局异常处理类
* 测试业务异常触发 熔断;
* 正常情况,1、2会正常请求;3会熔断;4正常请求;5熔断;
* 报错返回:CustomExceptionHandler/SentinelExceptionHandler里面的handlerDegradeException方法“{"msg":"被熔断","code":429}”;
*/
@Test
void 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"
* }
* ]
*/
@PostConstruct
private 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); // 限制特定 IP
AuthorityRuleManager.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次时限流
*/
@PostConstruct
private 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
*/
@Test
void 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();
}