Sentinel系列写到第五篇,前几篇我们把流控规则、降级规则、热点规则都过了一遍,到这篇剩下的就是授权规则和系统规则两个模块。标题上虽然写了“简要说明”,但以我实际排查问题的经验来看,这两个模块看着简单,用起来翻车的概率比流控规则还高。很多人配了授权规则不生效,配了系统规则发现接口全被拦了,问题往往出在没搞懂这两类规则在Sentinel整个拦截链里到底处于什么位置,以及它们背后依赖了哪些前置条件。这篇就用实战的方式把这两块拆开讲清楚,适合已经跑通Sentinel基础流程、准备在生产环境细化管控的同学参考。
1. 授权规则与系统规则:流控之外的“横向兜底”
从Sentinel的规则体系来看,流控、降级、热点规则针对的是单个资源,解决的是“某个接口能不能扛住这个量”的问题;而授权规则和系统规则解决的是另一类问题:一个请求到底有没有资格进来,以及这个应用整体还能不能撑住。这两类规则是横向的,作用在整个调用链上,不单纯挂在某一个资源名下。
1.1 授权规则到底在管什么
授权规则的核心是“我不能让谁进来”,区分白名单和黑名单两种模式。举个例子,线上有个下单接口,原本只应该由订单服务内部调用,结果被外部某个脚本盯上了,每天定时刷你几千次。流量不大,触发不了流控规则,但接口逻辑确实在被白嫖,业务上也说不清这些请求到底是哪来的。这时候授权规则就能派上用场:给请求来源打上标记,在Sentinel入口处直接拒绝黑名单来源,或者反过来,只允许白名单来源访问。
它的设计思路是“来源识别 + 黑白名单判断”,所以有一个关键前提:请求里必须能被解析出“来源应用”。这个来源可以是一个Header、一个参数、甚至一段自定义逻辑。如果应用里没做来源解析,授权规则就是一堆永远不生效的配置。
1.2 系统规则是给“整机”设的保险丝
系统规则的价值不在于保护某个接口,而在于保护整个应用进程。它的阈值维度包括系统Load、CPU使用率、入口QPS、平均RT、线程数,五个维度任何一项超标,都会触发全局拦截。典型的场景是业务大促期间,某个接口流量暴涨,流控规则还没来得及在对应的资源上生效,整个应用就快被拖垮了。这时系统规则像保险丝一样先跳闸,保住进程不被打挂。
这套机制的思路是“机器级别的兜底”,不分接口、不分调用方,一视同仁地限制入口流量。但它也有偏科的地方,比如你设置了入口QPS为1000,结果所有Web接口的请求都会算进这1000,如果某个接口本身会调用外部服务导致RT很高,也可能把全局平均RT拉高触发拦截。所以系统规则的阈值设置比流控规则更依赖对整机负载的理解,不能拍脑袋填数字。
2. 授权规则从配置到落地:请求来源解析是关键
授权规则看起来只有两个选项:白名单、黑名单,再加一个来源应用名称。但实际上最容易出问题的环节是“来源应用名称从哪来”。Sentinel不会自动知道调用方是谁,它需要你告诉它一个来源标识。
2.1 授权规则的判定逻辑
授权规则在Sentinel拦截链中位于统计完成之后、流控规则之前,核心类是AuthoritySlot。当一个请求进入时,Sentinel会调用RequestOriginParser解析出origin,然后拿着这个origin和当前资源的授权规则做匹配。
白名单模式下,limitApp里列出的来源允许通过,未列出的来源直接抛AuthorityException;黑名单模式正好反过来,列出的来源被拒绝,未列出的放行。
这里有个容易被忽略的细节:如果请求解析出来的origin为空或者为null,在白名单模式下请求会直接拒绝。原因很简单,白名单的语义是“只有列表里的才放行”,一个连来源都标识不出来的请求,自然不在信任范围内。所以实现RequestOriginParser时,至少要给一个兜底值,比如default,避免误伤正常请求。
2.2 来源解析:RequestOriginParser的实现细节
在Spring Boot的Web MVC场景下,spring-cloud-starter-alibaba-sentinel自动装配了SentinelWebInterceptor,它会从Spring容器中查找RequestOriginParser的实现类来解析来源。实现这个接口很简单:
@Component public class HeaderRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { // 优先取自定义Header,很多内部系统约定用X-Origin标识调用来源 String origin = request.getHeader("X-Origin"); if (origin != null && origin.trim().length() > 0) { return origin; } // 兜底返回一个固定值,避免白名单模式下误伤没有来源标识的请求 return "default"; } }这个类必须是Spring管理的Bean,否则SentinelWebInterceptor拿不到它,授权规则会静默失效。还有一个容易踩的坑:如果在项目里自定义了WebMvcConfigurer的addInterceptors方法,要注意别覆盖掉Sentinel的拦截器注册,否则整个Sentinel链路都不会生效。
另外,来源解析和资源名解析是两回事。资源名解析走的是UrlCleaner,用来把/order/create/123清洗成/order/create/*;来源解析走的是RequestOriginParser,只负责回答“这个请求是谁发来的”。这两个逻辑不要混在一起。
2.3 控制台配置步骤与白名单实战
假设现在有一个下单接口/order/create,我们希望只允许来源为gateway的请求访问,其他来源一律拒绝。操作很简单:
在控制台左侧菜单进入“授权规则”,点“新增授权规则”,资源名填/order/create,授权类型选“白名单”,来源应用填gateway,保存后这条规则就生效了。
规则底层逻辑等价于:
AuthorityRule rule = new AuthorityRule(); rule.setResource("/order/create"); rule.setLimitApp("gateway"); rule.setStrategy(RuleConstant.AUTHORITY_WHITE); AuthorityRuleManager.loadRules(Collections.singletonList(rule));保存后可以启动一个请求,不带X-Origin头去访问这个接口,会看到类似SentinelBlockException: AuthorityException的异常;带上X-Origin: gateway就能正常通过。
实践中我建议同时配置一套黑名单规则来封杀恶意调用者,比如来源标识为spider的请求:
AuthorityRule blackRule = new AuthorityRule(); blackRule.setResource("/order/create"); blackRule.setLimitApp("spider"); blackRule.setStrategy(RuleConstant.AUTHORITY_BLACK); AuthorityRuleManager.loadRules(Collections.singletonList(blackRule));这里有一个细节:授权规则的资源名必须和Sentinel埋点的资源名完全一致。Spring MVC适配器默认用请求URI作为资源名,所以/order/create这个写法没问题;如果项目里使用了自定义埋点SphU.entry("createOrder"),那授权规则的资源名就要填createOrder,而不是URL。
3. 系统规则拆解:五个维度的阈值怎么定、怎么生效
系统规则在控制台里看上去更简单:一行配置,五个可选项。但它的内部机制比授权规则复杂不少,因为涉及机器级指标的采集和入口流量的统计。
3.1 五个维度背后的含义
系统规则的五个维度分别是:
| 维度 | 参数名 | 含义 | 典型设置参考 |
|---|---|---|---|
| Load | highestSystemLoad | 系统当前Load(1分钟平均),仅Linux等类Unix系统有效 | 核数的1.5倍到2倍,比如4核机器设6.0 |
| CPU | highestCpuUsage | CPU使用率,0.0到1.0之间 | 0.6到0.8,留出缓冲 |
| RT | avgRt | 所有入口流量的平均响应时间,单位毫秒 | 业务P99耗时的1.5倍左右 |
| 线程数 | maxThreads | 入口流量的并发线程数 | 参考压测得出的最大并发数 |
| 入口QPS | qps | 所有入口流量的总QPS | 峰值QPS的70%左右 |
注意这里的RT和线程数都是“所有入口流量”的聚合指标,不是单个接口的指标。设置RT=300,意味着所有Web请求的平均RT超过300ms就会触发全局拦截,这在某些长接口业务里会显得很激进。比如有一个接口本身要跑5秒,但它的调用量很低,平均RT只有200ms,看起来不会触发;如果有一天这个慢接口调用量突然上来,平均RT被拉高到300ms以上,那所有接口都会被拦,这其实是合理的——因为整机已经被慢调用拖住了。
Load维度的判断逻辑是:只有当系统Load超过阈值且当前并发线程数超过系统容量时才会触发,单纯Load高但线程数不高,不触发。这个设计是防止机器负载瞬间尖峰导致误杀。在Windows上测试时,Load指标拿不到数据,所以源码里直接跳过这个维度的判断,也就是说你在Windows上调Load规则,它永远不会生效。
3.2 入口流量的统计机制
系统规则里反复提到“入口流量”,这个词很多人没细想。Sentinel把流量分为IN和OUT两种类型,Web请求默认是IN,微服务内部调用通常也是IN,而OUT代表出口调用。系统规则只统计入口流量,底层通过SystemMetricStatistics做滑动窗口聚合,默认统计窗口是1秒,每5秒检查一次阈值。
在默认的Spring MVC适配器场景下,所有被SentinelWebInterceptor拦截的请求都会自动标记为IN并累加到入口统计里。所以如果只配置了入口QPS=500,那应用上所有Web请求的总QPS超过500就会全部被拦下来。这个行为对单体应用来说很直观,但在微服务架构下要格外小心:你开了多个实例,每个实例的入口QPS是独立的,控制台上配置的500是单机值,不是集群值。
还有一个容易走弯路的地方:系统规则在控制台里只有“系统规则”一个入口,配置的规则是挂在默认入口资源Constants.ENTRY_RESOURCE上的。这个默认入口资源可以理解为“应用的总入口”,所有自动埋点的请求都会归到这里。但它也可以被替换,Sentinel提供了一个扩展点SystemRulesManager.registerSystemRulesProvider,大部分场景用不到,只需要知道有这么一个机制就好。
3.3 扩展入口规则与压测验证
我用一个实际案例来演示系统规则的完整落地过程。假设有一个4核8G的服务器,预期峰值入口QPS在800左右,线上要求P99 RT不高于300ms,CPU不能打满。先通过一次简单的压测拿到数据:压测工具设置200并发,跑5分钟,得到平均RT=180ms、入口QPS峰值=1050、CPU最高81%。这些数据就是设置阈值的依据。
控制台新增系统规则,配置一组合理阈值:
SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(6.0); rule.setHighestCpuUsage(0.8); rule.setAvgRt(300); rule.setMaxThreads(200); rule.setQps(800); SystemRuleManager.loadRules(Collections.singletonList(rule));保存后再次压测,当入口QPS超过800时,新请求开始被拒绝,控制台能看到SystemBlockException的拦截记录,CPU稳定在80%以下,服务不再响应超时。
这里要提醒一下:系统规则的阈值不是越快越好。如果设置得过低,比如CPU=0.5,业务上稍微有点波动就触发全局拦截,反而会影响正常流量。设置的原则是“在业务可接受的延迟范围内尽量多抗流量”,具体数值要基于压测数据迭代调整。
关于代码方式配置规则,生产环境有争议。用控制台配置的好处是改动即时生效、方便观察;用代码配置的好处是随应用启动一起加载,不依赖控制台连接。我的建议是测试环境用控制台快速调参,确定阈值后固化到配置文件里,用Sentinel的DataSource机制加载,避免生产环境控制台不稳定导致规则丢失。
4. 避坑指南:这两类规则最容易踩的坑
这两类规则看着简单,实际用起来问题不少。我整理了在多个项目里真实遇到的五类问题,基本可以覆盖大部分排查场景。
4.1 授权规则配了不生效
这是排在第一的高频问题。控制台里规则明明保存了,但请求进不来也出不去,心里没底。查这个问题的顺序一般是:
第一,确认有没有实现RequestOriginParser,并且类上标了@Component。没有实现请求来源解析,控制台填的来源应用名称就永远匹配不上,白名单模式下所有请求都会被拒绝,黑名单模式下规则形同虚设。
第二,确认请求来源解析返回值和控制台填写的值完全一致。解析逻辑里做了大小写转换或去空格,控制台填的是Gateway,解析返回的是gateway,匹配不上。建议解析逻辑只做trim,不做其他转换,避免自己给自己挖坑。
第三,确认Sentinel链路真的生效了。检查过滤器或拦截器有没有被自定义配置覆盖,最直接的办法是看请求日志里有没有Sentinel相关输出,或者故意配一个流控规则验证一下基础链路是否正常。
4.2 系统规则在Windows上不生效
很多人开发机是Windows,配了Load规则后发现压测怎么都触发不了,不是规则写错了,而是Windows没有Load这个概念,代码里检测到不支持就直接跳过了这个维度的判断。如果要在开发阶段验证系统规则,用CPU、RT、线程数、QPS这四个维度,Load维度放到Linux测试环境再验。
另外还要注意CPU使用率的获取在不同JDK版本下表现有差异,遇到CPU阈值始终不触发的情况,先看机器监控里CPU是不是真的超过了阈值,有时候虚拟机抢占导致监控工具看到的CPU很高,但Java层面拿到的数据有延迟。
4.3 网关场景的来源解析是另一套API
Spring Cloud Gateway场景下,Sentinel的接入方式不是SentinelWebInterceptor,而是SentinelGatewayFilter,请求来源解析的注册方式也完全不同。需要通过GatewayCallbackManager.setRequestOriginParser来注册:
@Configuration public class GatewayConfiguration { @PostConstruct public void init() { GatewayCallbackManager.setRequestOriginParser(exchange -> { String origin = exchange.getRequest().getHeaders().getFirst("X-Origin"); return origin != null ? origin : "default"; }); } }很多人在网关场景下依然按Web MVC的方式写@Component实现RequestOriginParser,结果授权规则完全不生效,就是因为这套适配器根本不会从Spring容器里去找那个接口。网关场景的资源名默认是路由ID,不是URL路径,配置授权规则时要用路由ID,否则也匹配不上。
4.4 阈值设置不能只靠感觉
系统规则的五个维度不是所有都要配,更建议只配置当前需要的那一两个维度,避免相互干扰。比如只配入口QPS和CPU两个维度,其他维度保持未配置状态,Sentinel会跳过未配置的判断。如果五个维度全部配满,可能出现某个维度先触发导致其他规则来不及验证的情况,给排查增加难度。
阈值方面常见的错误有两种:一种是把入口QPS设成单接口压测的吞吐量,但入口QPS是所有接口的总和,二者差好几倍;另一种是把RT设成单个接口的P99,但系统规则统计的是全局平均RT,和单接口的延迟不是一个概念。这两种错都会导致线上误触发,压测时需要特别区分。
4.5 授权规则和系统规则的配合思路
最后聊聊这两类规则的配合。授权规则、系统规则、流控规则的触发顺序是:授权规则最先判断,系统规则次之,流控规则最后。也就是说,一个被授权规则拒绝的请求,不会计入系统规则的入口统计,也不会进入流控判断;一个被系统规则拦截的请求,不会再继续往下走流控逻辑。
基于这个顺序,我通常会这样设计:最外层用授权规则挡住明确不允许的调用方,这是第一道闸;第二道用系统规则保护整机资源,这是保命闸;再往内才是流控、降级、热点这些精细化规则,管控单个接口的流量形态。
我在实际项目里第一次用系统规则时,把入口QPS设置成了2000,结果白天流量稍微一冲就直接全局拦截,业务方半夜打电话来找我。后来把阈值改到压测峰值的50%再逐步上调,用了一周才找到安全值。这个经验是:任何规则上线前都先在压测环境压一轮,拿到真实数据再填阈值,宁可先收紧再放宽,也不要一开始就给一个想当然的大数字。
生产环境里还有个小技巧,系统规则触发时会拒绝入口流量,但返回给调用方的异常是BlockException,如果你统一封装了全局异常处理器,记得把SystemBlockException也处理掉,否则调用方拿到的可能是500而不是预期中的提示信息。授权规则抛出的AuthorityException同理,也应该在异常处理里做统一兜底,给前端一个明确的提示,而不是裸抛异常。
这一篇把授权规则和系统规则讲完,Sentinel的规则体系就补得差不多了。下一篇准备聊规则持久化和动态数据源,这应该是生产环境里最后一块拼图了。