微服务高并发:授权与系统自适应功能的实现原理
授权与系统自适应
授权与系统自适应功能是 Sentinel 在实现限流与熔断降级功能之后提供的扩展性功能。
授权功能用于限制访问来源,而系统自适应功能可作为限流、熔断降级功能的兜底解决方案。
本章内容主要包括以下几个方面:
授权功能的实现原理。
系统自适应功能的实现原理。
授权功能的实现原理
黑白名单过滤是使用最为广泛的一种过滤规则,常见的有用于实现接口安全的 IP 黑白名单过滤和用于防骚扰的来电拦截黑白名单过滤。
黑白名单授权用于限制资源可以被哪些应用访问、不可以被哪些应用访问,若为资源配置了黑名单,且请求来源在黑名单中,则拒绝请求;若为资源配置了白名单,且请求来源在白名单中,则放行请求。
Sentinel 实现黑白名单授权功能的一些关键类说明如下:
AuthoritySlot:实现黑白名单授权功能的处理器插槽。
AuthorityRule:授权规则类。
AuthorityRuleChecker:授权检查类。
AuthorityRuleManager:授权规则管理者,提供 loadRules API。
AuthorityException:授权异常,是 BlockException 类的子类。
授权规则
授权规则是 Sentinel 中最易于理解的一种规则。AuthorityRule 的配置项如下:
strategy:限流策略,白名单(AUTHORITY_WHITE)或黑名单(AUTHORITY_BLACK)。从父类 AbstractRule 继承的字段。
resource:资源名称,从父类继承而来。
limitApp:调用来源,从父类继承而来,在 AuthorityRule 中可配置多个,使用符号“,”分隔,表示调用来源黑名单或调用来源白名单。
当 strategy 被配置为 AUTHORITY_WHITE 时,limitApp 就是调用来源白名单;当 strategy 被配置为 AUTHORITY_BLACK 时,limitApp 就是调用来源黑名单。
例如,限制资源“GET:/hello”只允许服务 A 和服务 C 访问,则代码如下:
授权处理器插槽
在使用默认的 SlotChainBuilder 情况下,AuthoritySlot 在 ProcessorSlotChain 中会被放在 SystemSlot、FlowSlot 和 DegradeSlot 的前面,这是因为其优先级更高。
AuthoritySlot 优先级更高的原因:一是可授权限流不需要使用任何指标数据;二是可提升性能,在未授权的情况下没必要判断是否需要限流、熔断等,这与用户授权功能是一样的道理,若未登录,则无须判断是否有权限访问某个资源。
AuthoritySlot 的实现源码如下:
对 checkBlackWhiteAuthority 方法的分析如下:
从 AuthorityRuleManager 中获取当前配置的所有授权规则。
获取为当前资源配置的所有授权规则。
遍历授权规则,调用 AuthorityRuleChecker#passCheck 方法判断是否拒绝当前请求,若是,则抛出 AuthorityException 异常。
授权规则检查器
AuthorityRuleChecker 负责实现黑白名单的过滤逻辑,其 passCheck 方法的源码如下:
从源码中可以看出,passCheck 方法的实现比较简单。首先,从当前 Context 实例获取调用来源的名称,判断是否有必要采用黑白名单过滤逻辑;然后,使用 indexOf 方法先简单匹配一次黑名单或白名单,再切割黑名单或白名单数组实现精确匹配,如果当前调用来源在名单中,则先根据策略判断这份名单是黑名单还是白名单,再决定是否需要拒绝请求。这段代码在性能方面进行了以下优化:
只有在调用来源不为空,且规则配置了黑名单或白名单的情况下,才采用黑白名单的过滤逻辑。
使用 indexOf 方法先简单匹配一次黑名单或白名单,再切割黑名单或白名单数组实现精确匹配。
提示:实现黑白名单限流的前提是每个服务消费端在发起请求时都必须携带自身服务的名称。
系统自适应功能的实现原理
系统自适应是指系统能够在负载过高的情况下自动拒绝请求,在保证服务稳定运行的情况下尽量处理更多请求,一旦系统负载达到阈值,就拒绝请求。
SystemSlot 是实现系统自适应限流的入口。DegradeSlot 在 ProcessorSlotChain 中被放在 FlowSlot 的后面,作为限流的兜底解决方案,而 SystemSlot 在 ProcessorSlotChain 中被放在 FlowSlot 的前面,强制系统优先考虑目前的情况能否处理当前请求,使系统尽量在实现最大吞吐量的同时保证稳定性。
系统自适应限流规则
系统自适应限流规则针对所有流量类型为 IN 的资源生效,因此不需要配置规则的资源名称。SystemRule 定义的字段如下:
qps:按 QPS 限流的阈值,默认为-1,大于 0 才生效。
avgRt:按平均耗时限流的阈值,默认为-1,大于 0 才生效。
maxThread:按最大并行占用线程数限流的阈值,默认为-1,大于 0 才生效。
highestCpuUsage:按 CPU 使用率限流的阈值,取值范围为[0.0,1.0],默认为-1,大于或等于 0.0 才生效。
highestSystemLoad:按系统负载限流的阈值,默认为-1,大于 0.0 才生效。
如果配置了多个系统自适应限流规则,则每个配置项只取最小值。例如,若 3 个系统自适应限流规则都配置了 qps,则取这 3 个规则中最小的 qps 作为限流阈值,并在调用 SystemRuleManager# loadRules 方法加载规则时完成,源码如下:
配置 highestSystemLoad,若多个规则都配置,则取最小值。
配置 highestCpuUsage,若多个规则都配置,则取最小值。
配置 avgRt,若多个规则都配置,则取最小值。
配置 maxThread,若多个规则都配置,则取最小值。
配置 qps,若多个规则都配置,则取最小值。
系统自适应限流判断流程
当 SystemSlot#entry 方法被调用时,由 SystemSlot 调用 SystemRuleManager#checkSystem 方法判断是否需要限流,其流程如图所示:
SystemRuleManager#checkSystem 方法首先从全局的资源指标数据统计节点(Constants.ENTRY_NODE)中读取当前时间窗口的指标数据,然后判断总 QPS、平均耗时等指标数据是否达到阈值,或者总占用线程数是否达到阈值,如果达到阈值,则抛出 SystemBlockException。
除此之外,checkSystem 方法还可以根据系统的平均负载(LoadAvg)和 CPU 使用率(CPU usage)限流。SystemStatusListener#run 方法会被定时调用,负责获取系统的平均负载和 CPU 使用率。
SystemRuleManager#checkSystem 方法的源码如下:
获取系统的平均负载和 CPU 使用率
使用 top 命令可以查看系统的平均负载和 CPU 使用率,如图所示:
Load Avg:3 个浮点数,分别代表 1 分钟、5 分钟、15 分钟内系统的平均负载。
CPU usage:CPU 使用率,user 为用户线程的 CPU 使用率,sys 为系统线程的 CPU 使用率。
Sentinel 通过定时任务实现每秒钟使用 OperatingSystemMXBean API 获取系统的平均负载和 CPU 使用率,其源码如下:
getSystemLoadAverage:获取最近 1 分钟系统的平均负载。
getSystemCpuLoad:返回系统最近一段时间内的 CPU 使用率。此值的取值范围为[0.0,1.0],是一个双精度值,值为 0.0 表示在最近一段时间内,所有 CPU 都处于空闲状态,而值为 1.0 表示在最近一段时间内,所有 CPU 都处于 100%活动状态,如果系统最近的 CPU 使用率不可用,则该方法返回负值。
checkBbr
如果应用处理一次请求的平均耗时为 1 秒,且应用部署在单核 CPU 服务器上,那么要让应用达到 y QPS,理论上就需要 y 个线程并发执行,并使用 checkBbr 控制并行占用线程数在 y 内,源码如下:
Constants.ENTRY_NODE:统计整个应用(所有资源)全局指标数据的 Node。
Constants.ENTRY_NODE.maxSuccessQps:从秒级滑动窗口中获取最大成功请求数。
Constants.ENTRY_NODE.minRt:当前时间窗口的最小请求处理耗时。
如果某接口的最大 QPS 为 800,处理一次请求的最小耗时为 5 毫秒,则至少需要并行的线程数与 Min RT 和 Max QPS 的关系满足:Max QPS=Threads×(1000/Min Rt)。
推出:Threads=Max QPS/(1000/Min Rt)=Max QPS×Min Rt/1000。
将 Min Rt 为 5 毫秒、Max QPS 为 800 代入上述公式,通过计算可得 Threads 等于 4。
所以,在 checkBbr 方法中,(minRt/1000)用于将最小耗时由毫秒转换为秒,表示系统在处理最多请求时的最小耗时,Constants.ENTRY_NODE.maxSuccessQps×(Constants.ENTRY_NODE.minRt/1000)表示至少需要每秒多少个线程并行才能达到 Constants.ENTRY_NODE.maxSuccessQps。在系统负载比较高的情况下,只要并行占用的线程数超过该值就限流。但如果负载高不是由当前进程引起的,则 checkBbr 方法的效果就不明显。
评论