简介

这篇文章主要介绍 Sentinel 核心库的使用。如果希望有一个最快最直接的了解,可以参考 新手指南 来获取一个最直观的感受。

我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:

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

先把可能需要保护的资源定义好(埋点),之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。

对于主流的框架,我们提供适配,只需要按照适配中的说明配置,Sentinel 就会默认定义提供的服务,方法等为资源。

定义资源

为了减少开发的复杂程度,我们对大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。您只需要引入对应的依赖即可方便地整合 Sentinel。可以参见: 。

方式二:抛出异常的方式定义资源

包含了 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)

手动 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. entry.exit();
  18. }
  19. }

热点参数埋点示例:

  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() 的参数描述:

注意SphU.entry(xxx) 需要与 entry.exit() 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。常见的错误:

  • 自定义埋点只调用 SphU.entry(),没有调用 entry.exit()
  • 顺序错误,比如:entry1 -> entry2 -> exit1 -> exit2,应该为 entry1 -> entry2 -> exit2 -> exit1

方式三:返回布尔值方式定义资源

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. }

注意SphO.entry(xxx) 需要与 SphO.exit()方法成对出现,匹配调用,位置正确,否则会导致调用链记录异常,抛出ErrorEntryFreeException` 异常。

方式四:注解方式定义资源

Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 函数来进行限流之后的处理。示例:

  1. // 原本的业务方法.
  2. @SentinelResource(blockHandler = "blockHandlerForGetUser")
  3. public User getUserById(String id) {
  4. throw new RuntimeException("getUserById command failed");
  5. }
  6. // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
  7. public User blockHandlerForGetUser(String id, BlockException ex) {
  8. return new User("admin");
  9. }

注意 blockHandler 函数会在原方法被限流/降级/系统保护的时候调用,而 fallback 函数会针对所有类型的异常。请注意 blockHandlerfallback 函数的形式要求,更多指引可以参见 Sentinel 注解支持文档

方式五:异步调用支持

Sentinel 支持异步调用链路的统计。在异步调用中,需要通过 SphU.asyncEntry(xxx) 方法定义资源,并通常需要在异步的回调函数中调用 exit 方法。以下是一个简单的示例:

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. // Asynchronous invocation.
  17. doAsync(userId, result -> {
  18. // 在异步回调中进行上下文变换,通过 AsyncEntry 的 getAsyncContext 方法获取异步 Context
  19. ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
  20. try {
  21. // 此处嵌套正常的资源调用.
  22. handleResult(result);
  23. } finally {
  24. entry.exit();
  25. }
  26. });
  27. });
  28. } catch (BlockException ex) {
  29. // Request blocked.
  30. // Handle the exception (e.g. retry or fallback).
  31. }
  32. }

此时的调用链就类似于:

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

更详细的示例可以参考 Demo 中的 ,里面包含了普通资源与异步资源之间的各种嵌套示例。

Sentinel 支持以下几种规则:流量控制规则熔断降级规则系统保护规则来源访问控制规则热点参数规则

流量规则的定义

重要属性:

同一个资源可以同时有多个限流规则,检查规则时会依次检查。

通过代码定义流量控制规则

理解上面规则的定义之后,我们可以通过调用 FlowRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则,比如:

  1. private void initFlowQpsRule() {
  2. List<FlowRule> rules = new ArrayList<>();
  3. FlowRule rule = new FlowRule(resourceName);
  4. // set limit qps to 20
  5. rule.setCount(20);
  6. rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
  7. rule.setLimitApp("default");
  8. FlowRuleManager.loadRules(rules);
  9. }

更多详细内容可以参考 流量控制

熔断降级规则 (DegradeRule)

熔断降级规则包含下面几个重要的属性:

同一个资源可以同时有多个降级规则。

理解上面规则的定义之后,我们可以通过调用 DegradeRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

更多详情可以参考 。

系统保护规则 (SystemRule)

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则包含下面几个重要的属性:

理解上面规则的定义之后,我们可以通过调用 SystemRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

  1. private void initSystemRule() {
  2. List<SystemRule> rules = new ArrayList<>();
  3. SystemRule rule = new SystemRule();
  4. rule.setHighestSystemLoad(10);
  5. rules.add(rule);
  6. SystemRuleManager.loadRules(rules);
  7. }

注意系统规则只针对入口资源(EntryType=IN)生效。更多详情可以参考 系统自适应保护文档

访问控制规则 (AuthorityRule)

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即规则的作用对象
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式

更多详情可以参考 。

热点规则 (ParamFlowRule)

详情可以参考 热点参数限流

查询更改规则

引入了 transport 模块后,可以通过以下的 HTTP API 来获取所有已加载的规则:

  1. http://localhost:8719/getRules?type=<XXXX>

其中,type=flow 以 JSON 格式返回现有的限流规则,degrade 返回现有生效的降级规则列表,system 则返回系统保护规则。

获取所有热点规则:

  1. http://localhost:8719/getParamRules

定制自己的持久化规则

  • 整合动态配置系统,如 ZooKeeper、、Apollo 等,动态地实时刷新配置规则
  • 结合 RDBMS、NoSQL、VCS 等来实现该规则
  • 配合 Sentinel Dashboard 使用

更多详情请参考 动态规则配置

在 Sentinel 中所有流控降级相关的异常都是异常类 BlockException 的子类:

  • 流控异常:FlowException
  • 熔断降级异常:DegradeException
  • 系统保护异常:SystemBlockException
  • 热点参数限流异常:ParamFlowException

我们可以通过以下函数判断是否为 Sentinel 的流控降级异常:

  1. BlockException.isBlockException(Throwable t);

除了在业务代码逻辑上看到规则生效,我们也可以通过下面简单的方法,来校验规则生效的效果:

  • 暴露的 HTTP 接口:通过运行下面命令 curl http://localhost:8719/cnode?id=<资源名称>,观察返回的数据。如果规则生效,在返回的数据栏中的 block 以及 block(m) 中会有显示
  • 日志:Sentinel 提供秒级的资源运行日志以及限流日志,详情可以参考:

block 事件

Sentinel 提供以下扩展接口,可以通过 StatisticSlotCallbackRegistryStatisticSlot 注册回调函数:

  • ProcessorSlotEntryCallback: callback when resource entry passed (onPass) or blocked (onBlocked)
  • ProcessorSlotExitCallback: callback when resource entry successfully completed (onExit)

可以利用这些回调接口来实现报警等功能,实时的监控信息可以从 ClusterNode 中实时获取。

其它 API

业务异常统计 Tracer

业务异常记录类 Tracer 用于记录业务异常。相关方法:

  • trace(Throwable e):记录业务异常(非 BlockException 异常),对应的资源为当前线程 context 下 entry 对应的资源。该方法必须在 SphU.entry(xxx) 成功之后且 exit 之前调用,否则当前 context 为空则会抛出异常。
  • trace(Throwable e, int count):记录业务异常(非 BlockException 异常),异常数目为传入的 count。该方法必须在 SphU.entry(xxx) 成功之后且 exit 之前调用,否则当前 context 为空则会抛出异常。
  • traceEntry(Throwable, int, Entry):向传入 entry 对应的资源记录业务异常(非 BlockException 异常),异常数目为传入的 count

如果用户通过 SphUSphO 手动定义资源,则 Sentinel 不能感知上层业务的异常,需要手动调用 Tracer.trace(ex) 来记录业务异常,否则对应的异常不会统计到 Sentinel 异常计数中。注意不要在 try-with-resources 形式的 SphU.entry(xxx) 中使用,否则会统计不上。

从 1.3.1 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex) 来记录业务异常。Sentinel 1.3.1 以前的版本需要手动记录。

上下文工具类 ContextUtil

相关静态方法:

标识进入调用链入口(上下文)

以下静态方法用于标识调用链路入口,用于区分不同的调用链路:

  • public static Context enter(String contextName)
  • public static Context enter(String contextName, String origin)

其中 contextName 代表调用链路入口名称(上下文名称),origin 代表调用来源名称。默认调用来源为空。返回值类型为 Context,即生成的调用链路上下文对象。

流控规则中若选择“流控方式”为“链路”方式,则入口资源名即为上面的 contextName

注意

  • ContextUtil.enter(xxx) 方法仅在调用链路入口处生效,即仅在当前线程的初次调用生效,后面再调用不会覆盖当前线程的调用链路,直到 exit。Context 存于 ThreadLocal 中,因此切换线程时可能会丢掉,如果需要跨线程使用可以结合 runOnContext 方法使用。
  • origin 数量不要太多,否则内存占用会比较大。

退出调用链(清空上下文)

  • public static void exit():该方法用于退出调用链,清理当前线程的上下文。

获取当前线程的调用链上下文

  • public static Context getContext():获取当前线程的调用链路上下文对象。

在某个调用链上下文中执行代码

  • public static void runOnContext(Context context, Runnable f):常用于异步调用链路中 context 的变换。

指标统计配置

Sentinel 底层采用高性能的滑动窗口数据结构来统计实时的秒级指标数据,并支持对滑动窗口进行配置。主要有以下两个配置:

  • windowIntervalMs:滑动窗口的总的时间长度,默认为 1000 ms

Dashboard

详情请参考:Sentinel Dashboard 文档