前言
什么是Sentinel,翻译过来就是哨兵的意思,随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。这里的Sentinel和Redis的Sentinel有所不同,其功能要点类似于Hystrix,二者的区别在后面细说。
这里引用一张官方图片,可以直观的看到Sentinel在微服务体系中的作用。
更多文档资料可以查看官方文档,这里对于其使用只做简单描述,重点分析实现原理层面的内容。
基本使用
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel主要包含的规则控制主要包括:
- 流控规则
- 熔断规则
- 授权规则
官方Demo下载地址。
服务端
下载
https://github.com/alibaba/Sentinel/releases
启动
启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
执行命令启动Sentinel控制台。
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
客户端
引入JAR包
对于SpringBoot或者Spring Cloud项目,可以使用starter的方式引入,整合了相关的jar。
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId><version>2.2.7.RELEASE</version></dependency>
需要说明的是Sentinel除了支持传统的Servlet项目外,也已经支持响应式web框架Spring Webflux。而且还加入了对Feign的适配。Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 2 个步骤。
- 配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabled=true
加入 spring-cloud-starter-openfeign 依赖使 Sentinel starter 中的自动化配置类生效。
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
FeignClient代码实例 ```java @FeignClient(name = “service-provider”, fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @RequestMapping(value = “/echo/{str}”, method = RequestMethod.GET) String echo(@PathVariable(“str”) String str); }
class FeignConfiguration { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } }
class EchoServiceFallback implements EchoService { @Override public String echo(@PathVariable(“str”) String str) { return “echo fallback”; } }
更多关于[Spring Cloud Sentinel](https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel)的文档。- 对于传统项目,可以按需引入需要的依赖,Sentinel定义了一系列的jar包。- [核心功能模块](https://github.com/alibaba/Sentinel/wiki/%E6%96%B0%E6%89%8B%E6%8C%87%E5%8D%97#%E6%9C%AC%E5%9C%B0-demo),这个模块封装了Sentinel的核心功能实现。```xml<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>1.8.3</version></dependency>
@SentinelResource 注解,通过切面注解支持,Sentinel提供了一种更优雅的方式避免硬编码。
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-annotation-aspectj</artifactId><version>1.8.3</version></dependency>
Transport通信模块,客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信。
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-transport-simple-http</artifactId><version>x.y.z</version></dependency>
Web Servlet原生整合
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-web-servlet</artifactId><version>1.8.1</version></dependency>
-
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-spring-webflux-adapter</artifactId><version>1.8.1</version></dependency>
应用案例
客户端在使用Sentinel的时候,提供了两种支持方式,一种是通过硬编码的方式(SphU),一种是使用注解(@SentinelResource),通过切面增强的方式。其实无论那种方式,使用思路都是一样的,可以概括为以下几个步骤。
- 定义资源
- 定义规则
- 检验规则是否生效
定义资源
下面介绍定义资源的几种方式。自动适配
为了减少开发的复杂程度,Sentinel适配了大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 、Quarkus等都做了适配。只需要引入对应的依赖即可方便地整合 Sentinel。详细内容可以查看 主流框架的适配。SphU
SphU 包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。
若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。try (Entry entry = SphU.entry("resourceName")) {// 被保护的业务逻辑// do something here...} catch (BlockException ex) {// 资源访问阻止,被限流或被降级// 在此处进行相应的处理操作}
手动 exit 示例
Entry entry = null;// 务必保证 finally 会被执行try {// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效entry = SphU.entry("自定义资源名");// 被保护的业务逻辑// do something...} catch (BlockException ex) {// 资源访问阻止,被限流或被降级// 进行相应的处理操作} catch (Exception ex) {// 若需要配置降级规则,需要通过这种方式记录业务异常Tracer.traceEntry(ex, entry);} finally {// 务必保证 exit,务必保证每个 entry 与 exit 配对if (entry != null) {entry.exit();}}
热点参数埋点示例
Entry entry = null;try {// 若需要配置例外项,则传入的参数只支持基本类型。// EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效// count 大多数情况都填 1,代表统计为一次调用。entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);// Your logic here.} catch (BlockException ex) {// Handle request rejection.} finally {// 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。if (entry != null) {entry.exit(1, paramA, paramB);}}
SphU.entry() 的参数描述
| 参数名 | 类型 | 解释 | 默认值 |
|---|---|---|---|
| entryType | EntryType | 资源调用的流量类型,是入口流量(EntryType.IN)还是出口流量(EntryType.OUT),注意系统规则只对 IN 生效 | EntryType.OUT |
| count | int | 本次资源调用请求的 token 数目 | 1 |
| args | Object[] | 传入的参数,用于热点参数限流 | 无 |
注意:SphU.entry(xxx) 需要与 entry.exit() 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。常见的错误:
- 自定义埋点只调用 SphU.entry(),没有调用 entry.exit()
- 顺序错误,比如:entry1 -> entry2 -> exit1 -> exit2,应该为 entry1 -> entry2 -> exit2 -> exit1
SphO
SphO 提供 if-else 风格的 API。用这种方式,当资源发生了限流之后会返回 false,这个时候可以根据返回值,进行限流之后的逻辑处理。// 资源名可使用任意有业务语义的字符串if (SphO.entry("自定义资源名")) {// 务必保证finally会被执行try {/*** 被保护的业务逻辑*/} finally {SphO.exit();}} else {// 资源访问阻止,被限流或被降级// 进行相应的处理操作}
@SentinelResource
Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理。 ```java // 原本的业务方法. @SentinelResource(blockHandler = “blockHandlerForGetUser”) public User getUserById(String id) { throw new RuntimeException(“getUserById command failed”); }
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用 public User blockHandlerForGetUser(String id, BlockException ex) { return new User(“admin”); }
注意 blockHandler 函数会在原方法被限流/降级/系统保护的时候调用,而 fallback 函数会针对所有类型的异常。请注意 blockHandler 和 fallback 函数的形式要求,更多指引可以参见 [Sentinel 注解支持文档](https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81)。<a name="LrKzN"></a>##### 异步调用资源Sentinel 支持异步调用链路的统计。在异步调用中,需要通过 SphU.asyncEntry(xxx) 方法定义资源,并通常需要在异步的回调函数中调用 exit 方法。```javatry {AsyncEntry entry = SphU.asyncEntry(resourceName);// 异步调用.doAsync(userId, result -> {try {// 在此处处理异步调用的结果.} finally {// 在回调结束后 exit.entry.exit();}});} catch (BlockException ex) {// Request blocked.// Handle the exception (e.g. retry or fallback).}
SphU.asyncEntry(xxx) 不会影响当前(调用线程)的 Context,因此以下两个 entry 在调用链上是平级关系(处于同一层),而不是嵌套关系。
// 调用链类似于:// -parent// ---asyncResource// ---syncResourceasyncEntry = SphU.asyncEntry(asyncResource);entry = SphU.entry(normalResource);
若在异步回调中需要嵌套其它的资源调用(无论是 entry 还是 asyncEntry),只需要借助 Sentinel 提供的上下文切换功能,在对应的地方通过 ContextUtil.runOnContext(context, f) 进行 Context 变换,将对应资源调用处的 Context 切换为生成的异步 Context,即可维持正确的调用链路关系。
public void handleResult(String result) {Entry entry = null;try {entry = SphU.entry("handleResultForAsync");// Handle your result here.} catch (BlockException ex) {// Blocked for the result handler.} finally {if (entry != null) {entry.exit();}}}public void someAsync() {try {AsyncEntry entry = SphU.asyncEntry(resourceName);// Asynchronous invocation.doAsync(userId, result -> {// 在异步回调中进行上下文变换,通过 AsyncEntry 的 getAsyncContext 方法获取异步 ContextContextUtil.runOnContext(entry.getAsyncContext(), () -> {try {// 此处嵌套正常的资源调用.handleResult(result);} finally {entry.exit();}});});} catch (BlockException ex) {// Request blocked.// Handle the exception (e.g. retry or fallback).}}
此时的调用链就类似于
-parent---asyncInvocation-----handleResultForAsync
定义规则
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API来定制自己的规则策略。
Sentinel 支持以下几种规则
- 流量控制规则
- 熔断降级规则
- 系统保护规则
- 来源访问控制规则
- 热点参数规则
更多内容查看规则的定制。
配置启动参数
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口(默认是 8719)。
IDEA启动时可以在配置项加入启动参数
对于SpringBoot或者Spring Cloud应用可以使用yml文件来配置Sentinel。
spring:cloud:sentinel:transport:port: 8719dashboard: localhost:8080

