Hi,我是空夜!
本节示例代码在 https://github.com/laolunsi/spring-boot-examples
首先下载 sentinel jar包:https://github.com/alibaba/Sentinel/releases
java -jar sentinel-xx.jar 运行,打开浏览器,输入默认地址:http://localhost:8080
sentinel 进行流量控制有以下流程:
- 定义资源
- 定义规则
- 检验规则是否生效
概念
资源,是 sentinel 的核心概念之一,可以简单的理解为一段代码。
sentinel 对主流的框架都提供了适配,下面以 Spring Cloud 为例,记录在 Spring Cloud 微服务架构中如何使用 sentinel 进行流量控制。
资源
分为以下几种方式:
- 主流框架的默认配置
- 抛出异常方式定义资源
- 返回布尔值方式定义资源
- 注解方式定义资源
- 异步调用支持
注解方式定义资源主要用于接口上,对接口进行流量控制,使用的是 @ResourceSentinel 注解。该注解提供了可选的异常处理和 fallback 配置项。参考:Sentinel 注解支持
源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
// 资源名称,不能为空
String value() default "";
EntryType entryType() default EntryType.OUT;
int resourceType() default 0;
// 可选项,处理 BlockException 的函数名称,默认在本类中;如果想要指定其他类中的函数,需要使用 blockHandlerClass 属性
String blockHandler() default "";
Class<?>[] blockHandlerClass() default {};
// 可选项,fallback 函数名称,用于在抛出异常时提供 fallback 处理逻辑。可以针对所有类型的异常。配合 fallbackClass 属性可以指定其他类中的函数
String fallback() default "";
String defaultFallback() default "";
Class<?>[] fallbackClass() default {};
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
// 忽略异常,即 fallback 和 blockHandler 函数不起作用的异常
Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
注意:
- blockHandler 和 fallback 指定的函数默认在本类中,该函数的返回值类型和参数顺序需与原方法保持一致。blockHandler 对应的函数可以在最后额外加一个 BlockException 类型的参数,fallback 对应的函数可以再最后额外加一个 Throwable 类型的参数
- 使用对应的 blockHandlerClass 和 fallbackClass 可以指定对应函数所在的类,而不是默认的当前类。注意,此时,指定的函数需要是 static 的,否则无法解析。
规则
- 流量控制规则 FlowRule:流量控制
原理是监控应用流量的 QPS 或并发线程数等指标,设置阈值,避免服务被瞬时的高峰流量冲垮。目的是保障高峰流量时应用的高可用性。
- 熔断降级规则 DegradeRule:熔断降级
目的是防止单个服务不可用导致的雪崩现象的发生,同样是保障应用高可用性的方法之一。
针对的是调用链路中不稳定的资源。
- 系统保护规则 SystemRule:系统自适应限流
系统保护规则从应用级别(而不是资源维度)的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
- 来源访问控制规则 AuthorityRule:黑白名单控制
根据调用来源判断请求是否允许通过。
- 热点参数规则 ParamFlowRule:热点参数限流
看作一种特殊的流量控制规则,仅对包含热点参数的资源调用生效。它根据传入参数中的热点参数,与配置的限流阈值、模式,对包含热点参数的资源调用进行限流。
sentinel 还提供了相关 API 用于定制自己的规则策略。
上面默认的规则分类,可以通过代码定义,也可以使用 dashboard 定义。
定义规则可以通过 dashboard,也可以使用代码创建。如果想要持久化存储规则,可以利用 Nacos 等数据源。这一点在后面会讲。
Spring Cloud 整合 Sentinel
参考:spring-cloud-alibaba-sentinel
创建一个 demo 服务,引入 spring-cloud 中的 sentinel 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
配置:
server:
port: 8203
spring:
application:
name: demo
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8081
可以配置流控、降级、热点、授权多种规则:
注解支持使用参考:注解支持 —— 官方文档
默认情况下是以 url 作为链路地址以及资源名的,我们可以在代码中用 @SentinelResource 注解来主动注明资源的名称、阻塞处理方法等。比如:
@RestController
@RequestMapping(value = "test")
@Validated
public class TestAction {
private static Logger logger = LoggerFactory.getLogger(TestAction.class);
@SentinelResource(value = "helloTest", blockHandler = "handleEx")
@GetMapping(value = "hello")
public String hello(@RequestParam("msg") @NotBlank String msg) {
logger.info("hellow, " + msg);
return "hello, " + msg;
}
@GetMapping(value = "send")
@SentinelResource(value = "sendTest", blockHandler = "handleException", blockHandlerClass = BlockHandlerConfig.class)
public String send(@NotBlank(message = "{required}") String email, @Email(message = "{invalid}") String msg) {
return "发消息给:" + email + ",消息内容:" + msg;
}
public String handleEx(String msg, BlockException ex) {
System.out.println("系统错误:msg=" + msg + ", ex: " + ex);
return "流量限制,请稍后重试";
}
}
这里的 helloTest 使用了本类中的 handleEx 方法作为阻塞处理方法。而 sendTest 用 BlockHandlerConfig 类中的 handleException 方法:
public class BlockHandlerConfig {
public static String handleException(String email, String msg, BlockException exception) {
return "444, " + exception.getClass().getCanonicalName() + "\t 服务不可用";
}
}
需要注意的是,blockHandle 对应方法的参数必须与 资源
的参数保持一致,否则规则不会生效,且会抛出异常。
这里给 helloTest 这个 resource 配置一个简单的流控:
测试:
快速请求 helloTest 对应的接口,发现成功请求与限流响应交错出现了。这表明我们的限流规则和 blockHandler 生效了。
规则持久化
需要注意的是,如果服务重启了,那么这些规则配置就会被丢失。其中一个解决办法是利用 Nacos 做配置中心,先将规则定义保存在 Nacos 中。
sentinel 提供了 file/nacos/zp/apollo 等规则扩展存储方式。具体参考官方文档:动态规则扩展
一个服务若要使用 Nacos 中的配置作为 sentinel 规则,除了 Nacos 的依赖外,还需要引入如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.0</version>
</dependency>
<!-- 为解决 Caused by: java.lang.ClassNotFoundException: com.alibaba.csp.sentinel.log.CommandCenterLog 引入 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
配置:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: sentinel-config
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
nacos 中新建一个 sentinel-config 配置文件,类型是 json,内容比如:
[
{
"resource": "helloTest",
"limitApp": "default",
"grade": 1,
"count": 3,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
启动后可以看到 sentinel 中出现了这个规则。在 nacos 中修改该规则,发现 sentinel 那边同步修改了(反之则不行)
FAQ —— sentinel 部署路径的问题
之前部署 sentinel 的时候,出现过这样一个问题:我配置了一个域名给微服务平台使用,其中的每一个模板,如 gateway, zipkin,sentinel,都是用 nginx 配置的 xxx.com/gateway/xxx 这样的请求格式来访问的。
打开 xxx.com/sentinel, 是可以看到 dashboard 和每个服务的,但是点开服务,发现监控数据没有了,簇点链路中每个地址的 QPS 都是0,点击新增流控规则,控制台报了 404 异常。
在官方仓库提了这个 issue,有位j叫 jasonjoo2010 的老哥给我解答了一下:
目前dashboard的静态部分,并不支持任意子目录部署的方式,建议在不改动的情况下,使用独立域名部署。
后来我新增了一个二级域名来专门访问 sentinel,就可以了。
关于这个问题的详细描述在 sentinel 官方仓库中:https://github.com/alibaba/Sentinel/issues/1804
参考:
- Sentinel 是什么?—— 官方文档
- 新手指南 —— 官方文档
- https://blog.csdn.net/xiongxianze/article/details/87570156
- https://blog.csdn.net/weixin_44757206/article/details/107119085
最近在系统地学习 Redis、RabbitMQ、ES 等技术的知识,着重关注原理、底层、并发等问题,关于相关技术分享后续会逐渐发布出来。欢迎关注公众号:猿生物语(ID:JavaApes)