【SpringCloud Alibaba】(八)学习 Sentinel 核心技术与配置规则(下)

目录

  • [1. 热点规则](#1. 热点规则)
    • [1.1 演示热点规则](#1.1 演示热点规则)
    • [1.2 演示热点高级选项规则](#1.2 演示热点高级选项规则)
  • [2. 授权规则](#2. 授权规则)
    • [2.1 演示授权规则](#2.1 演示授权规则)
  • [3. 系统规则](#3. 系统规则)
    • [3.1 演示系统规则](#3.1 演示系统规则)
  • [4. @SentinelResource 注解](#4. @SentinelResource 注解)
    • [4.1 @SentinelResource 注解概述](#4.1 @SentinelResource 注解概述)
    • [4.2 演示 @SentinelResource 注解](#4.2 演示 @SentinelResource 注解)
      • [4.2.1 定义限流和降级后的处理方法](#4.2.1 定义限流和降级后的处理方法)
      • [4.2.2 在外部类中指定限流和异常调用的方法](#4.2.2 在外部类中指定限流和异常调用的方法)
  • [5. Sentinel 持久化](#5. Sentinel 持久化)
    • [5.1 Sentinel 持久化概述](#5.1 Sentinel 持久化概述)
    • [5.2 实现 Sentinel 的持久化](#5.2 实现 Sentinel 的持久化)
  • 代码地址

Sentinel 的核心规则包括 流控规则、熔断规则、热点规则、授权规则和系统规则。每种规则的配置方式不同。

在上一篇文章中,我们已经介绍了 流控规则、熔断规则。接下来我们来继续了解剩下的核心规则

1. 热点规则

Sentinel 的热点规则可以根据 具体的参数 来控制流量规则,适用于根据不同参数进行流量控制的场景

1.1 演示热点规则

1、在订单微服务的 SentinelController 类中新增 requestSentinel3() 方法,如下所示:

java 复制代码
@GetMapping(value = "/request_sentinel3")
@SentinelResource("request_sentinel3")
public String requestSentinel3(String header, String body){
    log.info("测试Sentinel3");
    return "sentinel3";
}

2、在浏览器中访问 http://localhost:8080/order/request_sentinel3 接口,在 Sentinel 的簇点

链路中会显示 /request_sentinel3 接口

3、点击热点按钮,如下所示:

4、在弹出的热点规则配置框中的参数索引中输入 0,单机阈值输入 1,统计窗口时长输入 1,如下所

示:

表示:对 requestSentinel3() 方法的第一个参数 header 进行限流,如果每秒钟访问的次数超过 1 次,则触发限流

5、保存配置后,在浏览器中不断访问 http://localhost:8080/order/request_sentinel3? header=header ,当每秒访问的频率超过 1 次时,会触发 Sentinel 的限流操作,如下所示:

不断访问 http://localhost:8080/order/request_sentinel3?body=body ,则不会触发限流操作

1.2 演示热点高级选项规则

1、在弹出的热点规则配置框中打开高级选项,在参数类型中选择 java.lang.String,因为在参数索引中输入 0,表示的是对 header 参数限流,而 header 参数是 String 类型的。在参数值里输入 header,也就是为参数名为 header 的参数赋值为字符串 header。限流阈值为 1,如下所示:

2、点击保存按钮,在浏览器不断刷新 http://localhost:8080/order/request_sentinel3? header=header ,会触发 Sentinel 的限流操作

2. 授权规则

在某些场景下,需要根据 调用接口的来源 判断是否允许执行本次请求。此时就可以使用 Sentinel 提供的授权规则来实现,Sentinel 的授权规则能够根据请求的来源判断是否允许本次请求通过。

在 Sentinel 的授权规则中,提供了 白名单与黑名单 两种授权类型

2.1 演示授权规则

1、在订单微服务的 com.zzc.order.parse 包下新建 MyRequestOriginParser 类,如下所示:

java 复制代码
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getParameter("serverName");
    }

}

2、首先在浏览器中访问 http://localhost:8080/order/request_sentinel4 ,在 Sentinel 的簇点

链路里找到 /request_sentinel4

3、点击授权按钮,进入授权规则配置框,按照如下方式进行配置

其中,流控应用填写的是 test,授权类型为黑名单。

这里要结合新建的 MyRequestOriginParser 类进行理解,MyRequestOriginParser 类的 parseOrigin() 方法如下所示:

java 复制代码
public String parseOrigin(HttpServletRequest httpServletRequest) {
	return httpServletRequest.getParameter("serverName");
}

parseOrigin() 方法中直接返回了从 HttpServletRequest 中获取的 serverName 参数,而在上图中的流控应用中输出的是 test,授权类型为黑名单。

所以,如果我们访问 http://localhost:8080/order/request_sentinel4?serverName=test 的话,是处于黑名单的状态,无法访问

4、点击新增按钮后,不断在浏览器中刷新 http://localhost:8080/order/request_sentinel4? serverName=test ,会发现无法访问,被 Sentinel 限流了

3. 系统规则

系统保护规则是 应用整体维度 的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

  • Load (仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过
    系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护

3.1 演示系统规则

1、在订单微服务的 com.zzc.order.handler 包下新建 MyUrlBlockHandler 类,如下所示:

java 复制代码
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = null;
        if (e instanceof FlowException) {
            msg = "限流了";
        } else if (e instanceof DegradeException) {
            msg = "降级了";
        } else if (e instanceof ParamFlowException) {
            msg = "热点参数限流";
        } else if (e instanceof SystemBlockException) {
            msg = "系统规则(负载/...不满足要求)";
        } else if (e instanceof AuthorityException) {
            msg = "授权规则不通过";
        }
        // http状态码
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 500);
        jsonObject.put("codeMsg", msg);
        response.getWriter().write(jsonObject.toJSONString());
    }
}

2、在订单微服务的 SentinelController 类中新增 requestSentinel5() 方法,如下所示:

java 复制代码
@GetMapping(value = "/request_sentinel5")
@SentinelResource("request_sentinel5")
public String requestSentinel5(){
    log.info("测试Sentinel5");
    return "sentinel5";
}

3、首先在浏览器中访问 http://localhost:8080/order/request_sentinel5 ,在 Sentinel 的簇点链路里找到 /request_sentinel5

4、点击流控按钮,进入流控规则配置框,按照如下方式进行配置

5、在浏览器中不断刷新 http://localhost:8080/order/request_sentinel5 ,会显示如下信息:

说明触发了系统规则,捕获到了Sentinel全局异常。

4. @SentinelResource 注解

使用 Sentinel 时,可以使用 @SentinelResource 注解来指定异常处理策略

4.1 @SentinelResource 注解概述

在 Sentinel 中,指定发生异常时的处理策略非常简单,只需要使用 @SentinelResource 注解即可。

@SentinelResource 注解的源码如下所示:

java 复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
	//资源名称
	String value() default "";
	//entry类型,标记流量的方向,取值IN/OUT,默认是OUT
	EntryType entryType() default EntryType.OUT;
	int resourceType() default 0;
	//处理BlockException的函数名称,函数要求:
	//1. 必须是 public
	//2.返回类型 参数与原方法一致
	//3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置
	//blockHandlerClass ,并指定blockHandlerClass里面的方法。
	String blockHandler() default "";
	//存放blockHandler的类,对应的处理函数必须static修饰。
	Class<?>[] blockHandlerClass() default {};
	//用于在抛出异常的时候提供fallback处理逻辑。 fallback函数可以针对所
	//有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
	//1. 返回类型与原方法一致
	//2. 参数类型需要和原方法相匹配
	//3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定
	//fallbackClass里面的方法。
	String fallback() default "";
	//存放fallback的类。对应的处理函数必须static修饰。
	String defaultFallback() default "";
	//用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
	//行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
	//1. 返回类型与原方法一致
	//2. 方法参数列表为空,或者有一个 Throwable 类型的参数。
	//3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定
	fallbackClass 里面的方法。
	Class<?>[] fallbackClass() default {};
	//指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
	Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
	//需要trace的异常
	Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

4.2 演示 @SentinelResource 注解

4.2.1 定义限流和降级后的处理方法

1、在订单微服务的 com.zzc.order.service.SentinelService 接口中新增 sendMessage2()方法,SentinelServiceImpl 并实现之。

并且定义一个成员变量 count,用来记录请求 sendMessage2() 方法的次数,同时定义 25% 的异常率。

sendMessage2() 方法上使用 @SentinelResource 指定了资源的名称、发生 BlockException 时进入的方法和发生异常时进入的方法,代码如下所示:

java 复制代码
private int count = 0;

@Override
@SentinelResource(value = "sendMessage2", blockHandler = "blockHandler", fallback = "fallback")
public String sendMessage2() {
    count ++;
    //25%的异常率
    if (count % 4 == 0){
        throw new RuntimeException("25%的异常率");
    }
    return "sendMessage2";
}

public String blockHandler(BlockException e){
    log.error("限流了:{}", e);
    return "限流了";
}
public String fallback(Throwable e){
    log.error("异常了:{}", e);
    return "异常了";
}

3、在订单微服务的 com.zzc.order.controller.SentinelController 类中新增requestSentinel6() 方法,如下所示:

java 复制代码
@GetMapping(value = "/request_sentinel6")
public String requestSentinel6(){
    log.info("测试Sentinel6");
    return sentinelService.sendMessage2();
}

4、首先在浏览器中访问 http://localhost:8080/order/request_sentinel6 ,在 Sentinel 的簇点

链路里找到 /request_sentinel6

5、点击流控按钮进入流控规则页面,按照下图方式进行配置

6、点击新增按钮后在浏览器中刷新 http://localhost:8080/order/request_sentinel6 ,当刷新

的频率超过每秒 2 次时,浏览器会显示如下信息:

当刷新的次数是4的倍数时,浏览器会显示如下信息:

4.2.2 在外部类中指定限流和异常调用的方法

1、在订单微服务的 com.zzc.order.handler 包下新建 MyBlockHandlerClass类,用于定义被 Sentinel 限流时的方法,源码如下所示:

java 复制代码
@Slf4j
public class MyBlockHandlerClass {
	public static String blockHandler(BlockException e){
		log.error("限流了:{}", e);
		return "限流了";
	}
}

2、在订单微服务的 com.zzc.order.handler 包下新建 MyFallbackClass 类,用于定义抛出异常时调用的方法,源码如下所示:

java 复制代码
@Slf4j
public class MyFallbackClass {
	public static String fallback(Throwable e){
		log.error("异常了:{}", e);
		return "异常了";
	}
}

3、修改 SentinelServiceImpl#sendMessage2() 方法上的注解

java 复制代码
@Override
@SentinelResource(value = "sendMessage2",blockHandlerClass = MyBlockHandlerClass.class,
	blockHandler = "blockHandler",fallbackClass = MyFallbackClass.class,fallback = "fallback")
public String sendMessage2() {
	count ++;
	System.out.println(count);
	//25%的异常率
	if (count % 4 == 0){
		throw new RuntimeException("25%的异常率");
	}
	return "sendMessage2";
}

更上述步骤一致,可进行测试

5. Sentinel 持久化

Sentinel 中可以自定义配置的持久化来将 Sentinel 的配置规则持久化到服务器磁盘,使得重启应用或者 Sentinel 后,Sentinel 的配置规则不丢失

5.1 Sentinel 持久化概述

细心的小伙伴会发现:

我们之前配置的 Sentinel 规则在程序重启或者 Sentinel 重启后就会消失不见。

此时,就需要我们重新配置。如果这发生在高并发、大流量的场景下是不可接受的。那有没有什么办法让程序或 Sentinel 重启后配置不丢失呢?其实,Sentinel 中可以自定义配置的持久化来解决这个问题。

5.2 实现 Sentinel 的持久化

1、在订单微服务 shop-order 中新建 com.zzc.order.persistence 包,并创建
SentinelPersistenceRule 类,实现 com.alibaba.csp.sentinel.init.InitFunc 接口,并在
SentinelPersistenceRule 类中获取应用的名称,覆写 init() 方法,源码如下所示:

java 复制代码
public class SentinelPersistenceRule implements InitFunc {

    private String applicationName = "server-order";

    @Override
    public void init() throws Exception {
        String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + applicationName;
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);
        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new
                FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new
                FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new
                FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new
                FileWritableDataSource<>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
        // 系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new
                FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new
                FileWritableDataSource<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
        // 授权规则
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new
                FileRefreshableDataSource<>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new
                FileWritableDataSource<>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
        // 热点参数规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new
                FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new
                FileWritableDataSource<>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }
    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {});
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {});
    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(source, new TypeReference<List<SystemRule>>() {});
    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(source, new TypeReference<List<AuthorityRule>>() {});
    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {});

    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}

2、在订单微服务的 resources 目录下新建 META-INF 目录,并在 META-INF 目录下新建 services 目录,在 services 目录下新建名称为 com.alibaba.csp.sentinel.init.InitFunc 的文件,如下所示:

3、在 com.alibaba.csp.sentinel.init.InitFunc 文件中添加 com.zzc.order.persistence.SentinelPersistenceRule 类的全类名,如下所示。:

4、首先在浏览器中访问http://localhost:8080/order/request_sentinel6 ,在 Sentinel 的簇点

链路里找到 /request_sentinel6

5、点击流控按钮进入流控规则页面,按照下图方式进行配置:

6、点击新增按钮,此时打开电脑的 user.home 目录,我电脑的目录为C:\Users\Administrator ,可以发现 C :\Users\Administrator 目录中多了一个 sentinel-rules目录:

7、打开 sentinel-rules 目录,发现里面存在一个 server-order 目录。

8、打开 server-order 目录后,会发现生成了 Sentinel 的配置文件,并持久化到了磁盘上,如下所示:

9、打开 flow-rule.json 文件,内容如下所示:

json 复制代码
[
    {
        "clusterConfig": {
            "acquireRefuseStrategy": 0,
            "clientOfflineTime": 2000,
            "fallbackToLocalWhenFail": true,
            "resourceTimeout": 2000,
            "resourceTimeoutStrategy": 0,
            "sampleCount": 10,
            "strategy": 0,
            "thresholdType": 0,
            "windowIntervalMs": 1000
        },
        "clusterMode": false,
        "controlBehavior": 0,
        "count": 2.0,
        "grade": 1,
        "limitApp": "default",
        "maxQueueingTimeMs": 500,
        "resource": "/request_sentinel6",
        "strategy": 0,
        "warmUpPeriodSec": 10
    }
]

可以看到,flow-rule.json 文件中持久化了对于 /request_sentinel6 接口的配置。

重启订单微服务后,需要再次访问 http://localhost:8080/order/request_sentinel6 后,才能看见持久化后的配置

代码地址

代码已经上传至码云,码云地址

其中,数据库文件位于 db 文件夹下。

相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
追风筝的人er6 天前
企业管理系统如何实现自定义首页与千人千面?RuoYi Office 给出了完整方案
vue.js·spring boot·spring cloud
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode