【SpringCloud Alibaba】(八)学习 Sentinel 核心技术与配置规则(下)
目录
- 1. 热点规则
- 1.1 演示热点规则
- 1.2 演示热点高级选项规则
- 2. 授权规则
- 2.1 演示授权规则
- 3. 系统规则
- 3.1 演示系统规则
- 4. @SentinelResource 注解
- 4.1 @SentinelResource 注解概述
- 4.2 演示 @SentinelResource 注解
- 4.2.1 定义限流和降级后的处理方法
- 4.2.2 在外部类中指定限流和异常调用的方法
- 5. Sentinel 持久化
- 5.1 Sentinel 持久化概述
- 5.2 实现 Sentinel 的持久化
- 代码地址
Sentinel 的核心规则包括 流控规则、熔断规则、热点规则、授权规则和系统规则。每种规则的配置方式不同。
在上一篇文章中,我们已经介绍了 流控规则、熔断规则。接下来我们来继续了解剩下的核心规则
1. 热点规则
Sentinel 的热点规则可以根据 具体的参数 来控制流量规则,适用于根据不同参数进行流量控制的场景
1.1 演示热点规则
1、在订单微服务的 SentinelController 类中新增 requestSentinel3()
方法,如下所示:
@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
类,如下所示:
@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()
方法如下所示:
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
类,如下所示:
@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()
方法,如下所示:
@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
注解的源码如下所示:
@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
时进入的方法和发生异常时进入的方法,代码如下所示:
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()
方法,如下所示:
@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 限流时的方法,源码如下所示:
@Slf4j
public class MyBlockHandlerClass {
public static String blockHandler(BlockException e){
log.error("限流了:{}", e);
return "限流了";
}
}
2、在订单微服务的 com.zzc.order.handler
包下新建 MyFallbackClass 类,用于定义抛出异常时调用的方法,源码如下所示:
@Slf4j
public class MyFallbackClass {
public static String fallback(Throwable e){
log.error("异常了:{}", e);
return "异常了";
}
}
3、修改 SentinelServiceImpl#sendMessage2()
方法上的注解
@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()
方法,源码如下所示:
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
文件,内容如下所示:
[
{
"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
文件夹下。