阿里 Sentinel
是一款优秀的流控组件。在 Hystrix
及一众 Netflix OSS
组件被 Spring 弃用之后,它和 Resilience4j
成为我们日常微服务开发中选用的最常用流控组件。 而有时,Sentinel
可能因为我们使用不当,流控不能按我们预期的触发。在这里我总结了一些可能的原因。
未应用流控规则
设置流控规则可以通过编码实现,但通常我们更多配合 Dashboard 进行配置。在没有进行 push / pull 改造的情况下,应用节点重启,流控规则就会重置丢失。所以我们通常会进行一定改造,以将规则持久化到 Nacos 等,并在应用启动时加载恢复流控规则。
有时,可能因为权限变更、配置修改等,导致流控规则加载失败。加载不到配置时,如果是权限问题,日志中可以找到报错信息;如果是 dataId
配置错误或者配置中心改了 dataId
,应用日志却不会有输出,需要检查 sentinel-record.log
日志中 [XXXRuleManager] XXX rules loaded:
行的输出内容。
sentinel-record.log
日志默认路径为$HOME/logs/csp/sentinel-record.log.YYYY-MM-dd.SN
,之后不再重复说明
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:8848
# 如果 dataId 加载不到,日志也不会有错误输出!!需要检查 sentinel-record.log 。
spring.cloud.sentinel.datasource.ds1.nacos.dataId=${spring.application.name}-flow
spring.cloud.sentinel.datasource.ds1.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
spring.cloud.sentinel.datasource.ds1.nacos.username=${spring.cloud.nacos.username}
spring.cloud.sentinel.datasource.ds1.nacos.password=${spring.cloud.nacos.password}
sentinel-record.log
中输出加载到规则为空
$ grep 'rules loaded:' $HOME/logs/csp/sentinel-record.log.2024-05-23.0
2024-05-23 11:59:33.134 INFO [FlowRuleManager] Flow rules loaded: {}
2024-05-23 11:59:33.136 INFO [FlowRuleManager] Flow rules loaded: {}
2024-05-23 12:01:53.607 INFO [DegradeRuleManager] Degrade rules loaded: {}
2024-05-23 12:01:53.607 INFO [DegradeRuleManager] Degrade rules loaded: {}
2024-05-23 12:07:08.796 INFO [AuthorityRuleManager] Authority rules loaded: com.alibaba.csp.sentinel.slots.block.RuleManager@2016f509
2024-05-23 12:07:08.797 INFO [FlowRuleManager] Flow rules loaded: {}
2024-05-23 12:07:08.799 INFO [DegradeRuleManager] Degrade rules loaded: com.alibaba.csp.sentinel.slots.block.RuleManager@534c6767
2024-05-23 12:07:08.799 INFO [DefaultCircuitBreakerRuleManager] Default circuit breaker rules loaded: []
处理器方法名写错
开发同学在编码时有可能不小心结尾加一个空格、可能拼错个别字母,就会导致应该在调用处理器方法时找不到对应的方法。这种情况,需要我们关注sentinel-record.log
日志中是否出现找不到方法的警告输出(应用日志中无输出!)。
@Component
public class Demo {
// fallback 多了个空格
@SentinelResource(fallback = "addFallback ")
public int add(Integer a, Integer b) { return a + b; }
public int addFallback(Integer a, Integer b) { return -1; }
}
@Component
class DemoRun implements CommandLineRunner {
@Resource
private Demo demo;
@Override
public void run(String... args) throws Exception {
System.out.println(demo.add(1, null));
}
}
没有 fallback
调用,应用日志报错
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "b" is null
sentinel-record.log
包含找不到方法警告输出:
$ grep 'Cannot find' $HOME/logs/csp/sentinel-record.log.2024-05-23.0
2024-05-23 12:14:52.230 WARNING Cannot find method [addFallback ] in class [org.example.demo.handler_spell_mistake.Demo] with parameters [class java.lang.Integer, class java.lang.Integer]
2024-05-23 12:14:52.230 WARNING Cannot find method [addFallback ] in class [org.example.demo.handler_spell_mistake.Demo] with parameters [class java.lang.Integer, class java.lang.Integer, class java.lang.Throwable]
allbackClass 、 blockClass 误用
当使用 fallbackClass
、 blockHandlerClass
注解参数时,对应的 fallback
、 defaultFallback
、 blockHandler
必须是 static
方法,否则就会因为找不到方法忽略对应处理。排查方法与写错方法名一样,sentinel-record.log
日志中会有找不到方法的警告输出。
@Component
public class Demo {
// 这里把当前类设置到 fallbackClass 参数上 ,而 addFallback 方法因为不是 static 的而不会被调用
@SentinelResource(fallback = "addFallback", fallbackClass = Demo.class)
public int add(Integer a, Integer b) { return a + b; }
public int addFallback(Integer a, Integer b) { return -1; }
}
@Component
class DemoRun implements CommandLineRunner {
@Resource
private Demo demo;
@Override
public void run(String... args) throws Exception {
System.out.println(demo.add(1, null));
}
}
没有 fallback 调用,应用日志报错:
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "b" is null
sentinel-record.log
包含找不到方法警告输出:
$ grep 'Cannot find' $HOME/logs/csp/sentinel-record.log.2024-05-23.0
2024-05-23 12:07:08.800 WARNING Cannot find static method [addFallback] in class [org.example.demo.handler_class_static.Demo] with parameters [class java.lang.Integer, class java.lang.Integer]
2024-05-23 12:07:08.800 WARNING Cannot find static method [addFallback] in class [org.example.demo.handler_class_static.Demo] with parameters [class java.lang.Integer, class java.lang.Integer, class java.lang.Throwable]
关于 tatic
方法,还要注意,static
方法是没有方法覆盖重写的,不要在 fallbackClass
上指定子类来尝试查找父类的 static
方法。
另外,@SentinelResource
注解是可以用到类上的。其效果是:如果方法上没有指定 defaultFallback
时会尝试从类注解上确定 defaultFallback
。注意:方法注解中指定了 fallbackClass
(假设为 Foo
)、没有指定 defaultFallback
,类注解中指定了 defaultFallback
(假设为 bar
)、没有指定 fallbackClass
时,查找 defaultFallback
时不是查找当前类的方法 bar
,而查找 Foo
中的 static
方法 bar
。在类上使用注解的情况比较少见,但遇到了就会比较坑。在类上使用注解还有其他的坑点,我以后会另外写一篇文章讲一下。
异常捕获规则设置不当
@SentinelResource
支持设置 exceptionsToTrace
和 exceptionsToIgnore
参数,以控制异常捕获规则。这里要注意这两者的优先级:同时 trace 和 ignore 的异常类型,ignore 规则优先,即会被忽略。
如果 trace 的范围过大,或把 trace 范围配置的过小,会导致异常不被捕获,从而失去 block
、 fallback
处理。默认情况下,exceptionsToIgnore
为空 、 exceptionsToTrace
为 Throwable
(即 trace 所有异常、错误)。典型的配置错误是把 exceptionsToIgnore
设置为 Throwable
或把 exceptionsToTrace
设置为 []
。
异常捕获规则设置不当,通常表现为原始异常直接抛出,对应接口 500 报错。日志上目前我没有找到典型特征可供分析识别。
同名 handler 方法的遮蔽现象
blockHandler
只支持一种参数类型列表,即源方法参数类型列表 + BlockException
,根据方法名 + 参数类型列表可以唯一确定要查找的方法。
但 fallback
和 defaultFallback
都支持两种参数类型列表,即带异常参数和不带异常参数两种。以 fallback
为例,如果两种参数类型列表对应的方法都提供了,Sentinel
会优先使用不带异常参数的方法来作为处理方法,带异常类型参数的方法永远不会被调用。这种现象我称之为方法遮蔽(shadowing)(官方叫法我没找到)。
一些业务代码可能年代久远、不断迭代或实现不佳,导致结构冗长复杂,可能已经存在不带异常参数的处理方法(可能处于继承关系的上层)而开发同学没有注意到,当我们书写新的处理方法时它就会因为被遮蔽而不生效。
@Component
public class ShadowDemo implements CommandLineRunner {
@Resource
private MyAdder adder;
@Override
public void run(String... args) throws Exception {
System.out.println("result: " + adder.add(1, null));
}
@Component
public static class MyAdder extends Adder {
@SentinelResource(fallback = "fallback")
public int add(Integer a, Integer b) { return a + b; }
public int fallback(Integer a, Integer b, Throwable e) { return 0; }
}
public static class Adder {
public int fallback(Integer a, Integer b) { return -1; }
}
}
输出显示,调用了位于父类的 fallback(Integer, Integer)
方法,而不是新写的 fallback(Integer, Integer, Throwable)
方法:
result: 3
result: -1
目前,我没找到太好的特征在运行时、日志识别 shadowing 现象。
AOP 失效
AOP 失效的情况通常只在手动搭建最小依赖环境下出现,在实际产线环境比较罕见。在手动搭建最小依赖环境时,可能忘记引入 sentinel-annotation-aspectj
依赖或忘记声明 SentinelResourceAspect
Bean 而导致 AOP 不生效;而实现生产环境代码大多会使用 Spring boot starter ,一般是不会出现缺失依赖或者缺少配置的。
如何避坑?
以上,我总结了一些 Sentinel
流控失效原因和排查方案。但俗话说防患于未然,能不能在代码上线前编码时就识别这些问题呢。为了帮助“治病于初始”,我编写实现了一款 IDEA 插件 Alibaba Sentinel Annotation Support
,帮助大家正确使用 Sentinel
:
插件提供了跳转、自动补全和快速创建功能:
对无效 handler 值进行了检查,如果是包含首尾空白,还提供了快速修复:
检查 handler class 中不存在对应 static 方法时(即找不到 handler 方法)警告并提供快速创建选项:
异常捕获设置导致忽略所有异常时提供警告:
提供了 handler 遮蔽的检查提示:
此外还提供了 AOP 依赖、Bean 缺失检查,一些无效设置检查,value
hint,部分已知 bug 避坑提示等其他功能。
插件功能还在继续设计开发中,欢迎试用、评价。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。