前言

什么是Sentinel,翻译过来就是哨兵的意思,随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。这里的Sentinel和Redis的Sentinel有所不同,其功能要点类似于Hystrix,二者的区别在后面细说。
这里引用一张官方图片,可以直观的看到Sentinel在微服务体系中的作用。
image.png

更多文档资料可以查看官方文档,这里对于其使用只做简单描述,重点分析实现原理层面的内容。

基本使用

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel主要包含的规则控制主要包括:

  • 流控规则
  • 熔断规则
  • 授权规则

官方Demo下载地址

服务端

下载

https://github.com/alibaba/Sentinel/releases

启动

启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。

执行命令启动Sentinel控制台。

  1. java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

客户端

引入JAR包

  • 对于SpringBoot或者Spring Cloud项目,可以使用starter的方式引入,整合了相关的jar。

    1. <dependency>
    2. <groupId>com.alibaba.cloud</groupId>
    3. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    4. <version>2.2.7.RELEASE</version>
    5. </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 中的自动化配置类生效。

      1. <dependency>
      2. <groupId>org.springframework.cloud</groupId>
      3. <artifactId>spring-cloud-starter-openfeign</artifactId>
      4. </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”; } }

  1. 更多关于[Spring Cloud Sentinel](https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel)的文档。
  2. - 对于传统项目,可以按需引入需要的依赖,Sentinel定义了一系列的jar包。
  3. - [核心功能模块](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的核心功能实现。
  4. ```xml
  5. <dependency>
  6. <groupId>com.alibaba.csp</groupId>
  7. <artifactId>sentinel-core</artifactId>
  8. <version>1.8.3</version>
  9. </dependency>
  • @SentinelResource 注解,通过切面注解支持,Sentinel提供了一种更优雅的方式避免硬编码。

    1. <dependency>
    2. <groupId>com.alibaba.csp</groupId>
    3. <artifactId>sentinel-annotation-aspectj</artifactId>
    4. <version>1.8.3</version>
    5. </dependency>
  • Transport通信模块,客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信。

    1. <dependency>
    2. <groupId>com.alibaba.csp</groupId>
    3. <artifactId>sentinel-transport-simple-http</artifactId>
    4. <version>x.y.z</version>
    5. </dependency>
  • Web Servlet原生整合

    1. <dependency>
    2. <groupId>com.alibaba.csp</groupId>
    3. <artifactId>sentinel-web-servlet</artifactId>
    4. <version>1.8.1</version>
    5. </dependency>
  • Spring WebFlux的整合

    1. <dependency>
    2. <groupId>com.alibaba.csp</groupId>
    3. <artifactId>sentinel-spring-webflux-adapter</artifactId>
    4. <version>1.8.1</version>
    5. </dependency>

    应用案例

    客户端在使用Sentinel的时候,提供了两种支持方式,一种是通过硬编码的方式(SphU),一种是使用注解(@SentinelResource),通过切面增强的方式。其实无论那种方式,使用思路都是一样的,可以概括为以下几个步骤。

  1. 定义资源
  2. 定义规则
  3. 检验规则是否生效

    定义资源

    下面介绍定义资源的几种方式。
    自动适配
    为了减少开发的复杂程度,Sentinel适配了大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 、Quarkus等都做了适配。只需要引入对应的依赖即可方便地整合 Sentinel。详细内容可以查看 主流框架的适配
    SphU
    SphU 包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。
    1. // 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
    2. // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
    3. try (Entry entry = SphU.entry("resourceName")) {
    4. // 被保护的业务逻辑
    5. // do something here...
    6. } catch (BlockException ex) {
    7. // 资源访问阻止,被限流或被降级
    8. // 在此处进行相应的处理操作
    9. }
    若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。
  • 手动 exit 示例

    1. Entry entry = null;
    2. // 务必保证 finally 会被执行
    3. try {
    4. // 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
    5. // EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
    6. entry = SphU.entry("自定义资源名");
    7. // 被保护的业务逻辑
    8. // do something...
    9. } catch (BlockException ex) {
    10. // 资源访问阻止,被限流或被降级
    11. // 进行相应的处理操作
    12. } catch (Exception ex) {
    13. // 若需要配置降级规则,需要通过这种方式记录业务异常
    14. Tracer.traceEntry(ex, entry);
    15. } finally {
    16. // 务必保证 exit,务必保证每个 entry 与 exit 配对
    17. if (entry != null) {
    18. entry.exit();
    19. }
    20. }
  • 热点参数埋点示例

    1. Entry entry = null;
    2. try {
    3. // 若需要配置例外项,则传入的参数只支持基本类型。
    4. // EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效
    5. // count 大多数情况都填 1,代表统计为一次调用。
    6. entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
    7. // Your logic here.
    8. } catch (BlockException ex) {
    9. // Handle request rejection.
    10. } finally {
    11. // 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
    12. if (entry != null) {
    13. entry.exit(1, paramA, paramB);
    14. }
    15. }

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,这个时候可以根据返回值,进行限流之后的逻辑处理。
    1. // 资源名可使用任意有业务语义的字符串
    2. if (SphO.entry("自定义资源名")) {
    3. // 务必保证finally会被执行
    4. try {
    5. /**
    6. * 被保护的业务逻辑
    7. */
    8. } finally {
    9. SphO.exit();
    10. }
    11. } else {
    12. // 资源访问阻止,被限流或被降级
    13. // 进行相应的处理操作
    14. }
    @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”); }

  1. 注意 blockHandler 函数会在原方法被限流/降级/系统保护的时候调用,而 fallback 函数会针对所有类型的异常。请注意 blockHandler fallback 函数的形式要求,更多指引可以参见 [Sentinel 注解支持文档](https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81)。
  2. <a name="LrKzN"></a>
  3. ##### 异步调用资源
  4. Sentinel 支持异步调用链路的统计。在异步调用中,需要通过 SphU.asyncEntry(xxx) 方法定义资源,并通常需要在异步的回调函数中调用 exit 方法。
  5. ```java
  6. try {
  7. AsyncEntry entry = SphU.asyncEntry(resourceName);
  8. // 异步调用.
  9. doAsync(userId, result -> {
  10. try {
  11. // 在此处处理异步调用的结果.
  12. } finally {
  13. // 在回调结束后 exit.
  14. entry.exit();
  15. }
  16. });
  17. } catch (BlockException ex) {
  18. // Request blocked.
  19. // Handle the exception (e.g. retry or fallback).
  20. }

SphU.asyncEntry(xxx) 不会影响当前(调用线程)的 Context,因此以下两个 entry 在调用链上是平级关系(处于同一层),而不是嵌套关系。

  1. // 调用链类似于:
  2. // -parent
  3. // ---asyncResource
  4. // ---syncResource
  5. asyncEntry = SphU.asyncEntry(asyncResource);
  6. entry = SphU.entry(normalResource);

若在异步回调中需要嵌套其它的资源调用(无论是 entry 还是 asyncEntry),只需要借助 Sentinel 提供的上下文切换功能,在对应的地方通过 ContextUtil.runOnContext(context, f) 进行 Context 变换,将对应资源调用处的 Context 切换为生成的异步 Context,即可维持正确的调用链路关系。

  1. public void handleResult(String result) {
  2. Entry entry = null;
  3. try {
  4. entry = SphU.entry("handleResultForAsync");
  5. // Handle your result here.
  6. } catch (BlockException ex) {
  7. // Blocked for the result handler.
  8. } finally {
  9. if (entry != null) {
  10. entry.exit();
  11. }
  12. }
  13. }
  14. public void someAsync() {
  15. try {
  16. AsyncEntry entry = SphU.asyncEntry(resourceName);
  17. // Asynchronous invocation.
  18. doAsync(userId, result -> {
  19. // 在异步回调中进行上下文变换,通过 AsyncEntry 的 getAsyncContext 方法获取异步 Context
  20. ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
  21. try {
  22. // 此处嵌套正常的资源调用.
  23. handleResult(result);
  24. } finally {
  25. entry.exit();
  26. }
  27. });
  28. });
  29. } catch (BlockException ex) {
  30. // Request blocked.
  31. // Handle the exception (e.g. retry or fallback).
  32. }
  33. }

此时的调用链就类似于

  1. -parent
  2. ---asyncInvocation
  3. -----handleResultForAsync

定义规则

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API来定制自己的规则策略。
Sentinel 支持以下几种规则

  • 流量控制规则
  • 熔断降级规则
  • 系统保护规则
  • 来源访问控制规则
  • 热点参数规则

更多内容查看规则的定制

配置启动参数

启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口(默认是 8719)。
IDEA启动时可以在配置项加入启动参数
image.png
对于SpringBoot或者Spring Cloud应用可以使用yml文件来配置Sentinel。

  1. spring:
  2. cloud:
  3. sentinel:
  4. transport:
  5. port: 8719
  6. dashboard: localhost:8080

原理分析

设计架构

E1406023309847E02B7D69E2C2B1CE83.gif
E7D206B09E06F842109DA1D319068112.jpg

滑动时间窗口算法

源码分析

同类工具对比