阿里 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 误用

当使用 fallbackClassblockHandlerClass 注解参数时,对应的 fallbackdefaultFallbackblockHandler 必须是 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 支持设置 exceptionsToTraceexceptionsToIgnore 参数,以控制异常捕获规则。这里要注意这两者的优先级:同时 trace 和 ignore 的异常类型,ignore 规则优先,即会被忽略。

如果 trace 的范围过大,或把 trace 范围配置的过小,会导致异常不被捕获,从而失去 blockfallback 处理。默认情况下,exceptionsToIgnore 为空 、 exceptionsToTraceThrowable (即 trace 所有异常、错误)。典型的配置错误是把 exceptionsToIgnore 设置为 Throwable 或把 exceptionsToTrace 设置为 []

异常捕获规则设置不当,通常表现为原始异常直接抛出,对应接口 500 报错。日志上目前我没有找到典型特征可供分析识别。

同名 handler 方法的遮蔽现象

blockHandler 只支持一种参数类型列表,即源方法参数类型列表 + BlockException ,根据方法名 + 参数类型列表可以唯一确定要查找的方法。

fallbackdefaultFallback 都支持两种参数类型列表,即带异常参数和不带异常参数两种。以 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

插件提供了跳转、自动补全和快速创建功能:

blockHandler from Handlers

defaultFallback from parent

create fallback

对无效 handler 值进行了检查,如果是包含首尾空白,还提供了快速修复:
invalid handler check

检查 handler class 中不存在对应 static 方法时(即找不到 handler 方法)警告并提供快速创建选项:

should static

异常捕获设置导致忽略所有异常时提供警告:

ignore Throwable

trace none

提供了 handler 遮蔽的检查提示:

shadowing

此外还提供了 AOP 依赖、Bean 缺失检查,一些无效设置检查,value hint,部分已知 bug 避坑提示等其他功能。

插件功能还在继续设计开发中,欢迎试用、评价。


Dowen
4 声望0 粉丝