本文已收录在Github关注我,紧跟本系列专栏文章,咱们下篇再续!

  • 🚀 魔都架构师 | 全网30W技术追随者
  • 🔧 大厂分布式系统/数据中台实战专家
  • 🏆 主导交易系统百万级流量调优 & 车联网平台架构
  • 🧠 AIGC应用开发先行者 | 区块链落地实践者
  • 🌍 以技术驱动创新,我们的征途是改变世界!
  • 👉 实战干货:编程严选网

0 概述

FlowSlot根据预设规则,结合 NodeSelectorSlotClusterNodeBuilderSlotStatistcSlot 统计的实时信息进行流控。

限流直接表现在执行

Entry nodeA = SphU.entry(资源名字)

时抛FlowException。FlowExceptionBlockException 的子类,可捕捉 BlockException 来自定义被限流之后的处理逻辑。

// 限流降级方法
public ResultBody blockHandler(BlockException e) {
    log.warn("触发限流", e);
    return ResultBody.error("服务繁忙,请稍后再试");
}

同一个资源可对应多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。

限流规则组成

组合实现不同限流效果:

  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型,QPS 或线程数
  • strategy: 根据调用关系选择策略

1 基于QPS/并发数的流量控制

流控的统计类型:

  • 线程数
  • QPS

类型由 FlowRule.grade 字段定义:

private int grade = RuleConstant.FLOW_GRADE_QPS;

线程数、QPS 值都由 StatisticSlot 实时统计获取。

查看实时统计

curl http://localhost:8719/cnode?id=resourceName

输出内容格式如下:

idx id   thread  pass  blocked   success  total Rt   1m-pass   1m-block   1m-all   exeption
2   abc647 0     46     0           46     46   1       2763      0         2763     0

其中:

  • thread: 代表当前处理该资源的线程数;
  • pass: 代表一秒内到来到的请求;
  • blocked: 代表一秒内被流量控制的请求数量;
  • success: 代表一秒内成功处理完的请求;
  • total: 代表到一秒内到来的请求以及被阻止的请求总和;
  • RT: 代表一秒内该资源的平均响应时间;
  • 1m-pass: 则是一分钟内到来的请求;
  • 1m-block: 则是一分钟内被阻止的请求;
  • 1m-all: 则是一分钟内到来的请求和被阻止的请求的总和;
  • exception: 则是一秒内业务本身异常的总和。

2.1 并发线程数流量控制

保护业务线程数不被耗尽。如当应用所依赖下游应用由于某种原因导致服务不稳定、响应延迟增加,对caller,意味吞吐量下降和更多线程数占用,极端情况下甚至导致线程池耗尽。

为应对高线程占用,业内有隔离方案,如:

  • 通过不同业务逻辑使用不同线程池,隔离业务自身之间的资源争抢(线程池隔离)
  • 使用信号量控制同时请求的个数(信号量隔离)

这种隔离方案虽能控制线程数量,但无法控制请求排队时间。当请求过多时排队也无益,直接拒绝能迅速降低系统压力。Sentinel线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程个数,如果超出阈值,新的请求会被立即拒绝。

例:ThreadDemo

2.2 QPS流量控制

当 QPS 超阈值进行流量控制。流控手段对应 FlowRule 中的 controlBehavior 字段:

/**
 * Rate limiter control behavior.
 * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter
 */
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
2.2.1 直接拒绝

RuleConstant.CONTROL_BEHAVIOR_DEFAULT

默认方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,抛FlowException

  • 适用:对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时
  • 例:FlowqpsDemo
2.2.2 冷启动

RuleConstant.CONTROL_BEHAVIOR_WARM_UP

主要用于系统长期处低水位时,当流量突增,直接把系统拉升到高水位可能瞬间把系统压垮。

通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热时间,避免冷系统被压垮。参见WarmUpFlowDemo

通常冷启动的过程,系统允许通过的QPS曲线:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2.3 匀速器

RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER

严格控制请求通过的间隔时间,即让请求匀速通过,对应漏桶算法。

例:PaceFlowDemo

作用:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主要用于处理间隔性突发的流量,如MQ。

设想:某1s有大量请求到来,而接下来几秒则处空闲状态,我们希望系统能在接下来空闲期间逐渐处理这些请求,而非第1秒内直接拒绝多余请求。

2 基于调用关系的流控

  • 调用关系包括调用方、被调用方
  • 方法又可能会调用其它方法,形成一个调用链路的层次关系

Sentinel 通过 NodeSelectorSlot 建立不同资源间的调用的关系,并通过 ClusterNodeBuilderSlot 记录每个资源的实时统计信息。

有了调用链路的统计信息,我们可以衍生出多种流量控制手段。

3.1 调用方限流

ContextUtil.enter(resourceName, origin) 方法中的 origin 参数标明调用方身份。这些信息会在 ClusterBuilderSlot 中被统计。

不同调用方对同一个资源的调用数据:

curl http://localhost:8719/origin?id=nodeA

调用数据示例:

# 资源名为 `nodeA` 的资源被两个不同的调用方调用的统计
id: nodeA
idx origin  threadNum passedQps blockedQps totalQps aRt   1m-passed 1m-blocked 1m-total 
1   caller1 0         0         0          0        0     0         0          0
2   caller2 0         0         0          0        0     0         0          0

限流规则中的 limitApp 字段用于根据调用方进行流量控制。该字段的值有以下三种选项,分别对应不同的场景:

  • default:表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。
  • {some_origin_name}:表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如 NodeA 配置了一条针对调用者caller1的规则,那么当且仅当来自 caller1NodeA 的请求才会触发流量控制。
  • other:表示针对除 {some_origin_name} 以外的其余调用方的流量进行流量控制。例如,资源NodeA配置了一条针对调用者 caller1 的限流规则,同时又配置了一条调用者为 other 的规则,那么任意来自非 caller1NodeA 的调用,都不能超过 other 这条规则定义的阈值。

同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default

3.2 根据调用链路入口限流:链路限流

NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

典型的调用树:

machine-root
              /       \
             /         \
       Entrance1     Entrance2
          /             \
         /               \
DefaultNode(nodeA)   DefaultNode(nodeA)

来自入口Entrance1、Entrance2的请求都调用到资源NodeA,Sentinel可只按某入口的统计信息对资源限流。如设置 FlowRule.strategyRuleConstant.CHAIN,同时设置 FlowRule.ref_identityEntrance1 表示仅从入口 Entrance1 的调用才会记录到 NodeA 的限流统计,而不关心来自 Entrance2 的调用。

调用链的入口是通过ContextUtil.enter(name)定义。

3.3 具有关系的资源流量控制:关联流量控制

当两个资源之间具有资源争抢或依赖关系时,这两个资源便具有了关联。如对数据库同一字段的读、写操作存在争抢,读速度过高影响写速度,写速度过高会影响读速度。若放任读写操作争抢资源,则争抢本身带来的开销会降低整体吞吐量。

可用关联限流避免具有关联关系的资源之间过度的争抢,如read_db、write_db两个资源分别代表数据库读写,可给read_db设置限流规则来达到写优先,只需设置:

FlowRule.strategy = RuleConstant.RELATE
同时设置
FlowRule.ref_identity = write_db

当写库操作过于频繁时,读数据的请求会被限流。

更多推荐