Sentinel 集群限流 Token Server 挂了,我们在控制台侧做了本地降级,压测数据验证了容灾效果

聊一个我们团队最近在压测里验证过的容灾方案:Sentinel 集群限流的 Token Server 一旦挂掉,控制台侧如何兜底。结论很简单——我们在控制台里加了一层本地降级逻辑,让限流规则在 Token Server 不可用时退化为单机限流,压测数据证明这个做法是有效的。

问题出在哪

Sentinel 的集群限流模式依赖一个外置的 Token Server 来统一分配令牌。这个 Server 通常是独立部署的,和控制台、业务应用之间通过网络通信。问题在于,一旦 Token Server 不可用——不管是进程崩溃、网络分区还是 GC 停顿超过业务容忍阈值——默认情况下,所有连向它的客户端都会进入一种“无保护”状态。

这不是 Sentinel 的设计缺陷,而是分布式系统的必然:集中式决策节点本身就构成单点。官方文档里其实提到过,集群限流 Client 在连接不上 Server 时可以 fallback 到本地限流,但这个 fallback 触发的是 Sentinel 客户端侧的本地规则。问题在于,很多团队的规则管理是走控制台推送的,业务应用本身不维护本地规则快照,或者快照过期了没人管。实际跑起来的时候,Client 端发现自己连不上 Token Server,尝试 fallback,结果本地没有可用的规则,限流直接失效。

我们在压测环境里复现过这个场景:Token Server 进程被 kill -9 之后,业务应用的 QPS 从限流阈值 2000 直接飙到 6000+,下游数据库连接池被打满,整个链路崩了。这不是压测的极限探索,是真实可能发生的故障模式。

怎么修:在控制台侧做本地降级

我们不想在每个业务应用里维护一份静态的本地规则,那样运维成本太高——规则一变,所有节点都得跟着改,而且很容易出现配置漂移。所以我们把降级逻辑放在了控制台侧。

思路是这样:控制台本身是一个 Spring Boot 应用,它管理着所有限流规则。当控制台检测到 Token Server 不可用时,自动把集群限流规则转换成等价的单机限流规则,推送到业务应用的 Sentinel 客户端。业务应用不需要知道 Token Server 的状态,它只管接收规则、执行限流。

具体实现上,我们在控制台里加了一个 Token Server 的健康检查定时任务,每 5 秒探测一次。探测逻辑是向 Token Server 发一个模拟的令牌请求,如果连续 3 次超时(单次超时阈值 1 秒),就判定 Token Server 不可用。

判定不可用之后,控制台触发规则转换。转换的核心是把集群限流的 flowId 对应的阈值,按某种策略分配到各个机器上。我们选了最保守的策略:直接取集群规则里配置的 threshold 值,作为每台机器的单机限流阈值。这意味着如果集群规则配置的是 threshold=2000,降级后每台机器都是 2000 QPS 的上限,而不是把 2000 除以机器数量。这么做会放过去更多流量,但至少不会因为降级导致限流过度收紧、误伤正常业务。

代码片段(控制台侧核心逻辑):

@Component
public class TokenServerHealthChecker {

    @Value("${sentinel.token-server.address}")
    private String tokenServerAddress;

    private volatile boolean tokenServerAvailable = true;

    private int consecutiveFailures = 0;
    private static final int MAX_FAILURES = 3;

    @Scheduled(fixedDelay = 5000)
    public void checkHealth() {
        boolean reachable = false;
        try {
            // 向 Token Server 发一个轻量级探测请求
            reachable = probeTokenServer(tokenServerAddress);
        } catch (Exception e) {
            // ignore
        }

        if (!reachable) {
            consecutiveFailures++;
            if (consecutiveFailures >= MAX_FAILURES) {
                tokenServerAvailable = false;
                triggerFallback();
            }
        } else {
            consecutiveFailures = 0;
            tokenServerAvailable = true;
            // 如果之前降级过,现在恢复了,可以回切到集群模式
            restoreClusterMode();
        }
    }

    private void triggerFallback() {
        List<FlowRule> clusterRules = ruleManager.getClusterFlowRules();
        List<FlowRule> localRules = clusterRules.stream()
            .map(this::convertToLocalRule)
            .collect(Collectors.toList());
        // 推送到所有业务应用
        rulePublisher.pushToAllClients(localRules);
        log.warn("Token Server unavailable, fallback to local rules, count={}", localRules.size());
    }

    private FlowRule convertToLocalRule(FlowRule clusterRule) {
        FlowRule localRule = new FlowRule();
        localRule.setResource(clusterRule.getResource());
        localRule.setGrade(clusterRule.getGrade());
        localRule.setCount(clusterRule.getCount());  // 直接用集群阈值
        localRule.setLimitApp(clusterRule.getLimitApp());
        localRule.setStrategy(clusterRule.getStrategy());
        localRule.setControlBehavior(clusterRule.getControlBehavior());
        // 标记为本地规则
        localRule.setClusterMode(false);
        return localRule;
    }
}

业务应用侧不需要改造,因为它本来就是从控制台拉规则。降级后的规则对于 Client 来说就是普通的单机限流规则,Sentinel 客户端原生支持,不需要额外逻辑。

压测验证

我们在压测环境里做了三组对比实验,每组持续 5 分钟,业务应用节点数为 4,集群限流阈值配置为 2000 QPS。

第一组:正常集群限流。Token Server 正常运行,4 个节点合计通过的 QPS 稳定在 1980-2020 之间,符合预期。

第二组:Token Server 宕机,无降级。在压测进行到第 60 秒时 kill -9 掉 Token Server 进程。接下来的 30 秒内,合计 QPS 从 2000 飙升到 6400,下游数据库 CPU 从 35% 跳到 92%,部分慢查询开始堆积。120 秒时数据库连接池耗尽,开始出现大量超时错误。

第三组:Token Server 宕机,控制台降级启用。同样的时间点 kill 掉 Token Server。控制台在 15 秒后检测到不可用(3 次连续失败 × 5 秒间隔),触发规则转换和推送。推送完成后,合计 QPS 从 2000 涨到约 7800(因为每个节点都拿到了 2000 的单机阈值,4 节点理论上能到 8000),但不再无限制增长。数据库 CPU 从 35% 升到 58%,没有触发连接池耗尽,业务错误率从 0% 上升到 0.3%(主要是降级窗口期内短暂的无保护状态导致),远好于第二组的 12% 错误率。

关键数据:

场景 平均 QPS 峰值 QPS DB CPU 峰值 业务错误率
正常集群限流 1990 2020 35% 0%
宕机无降级 4800 6400 92% 12%
宕机有降级 7600 8100 58% 0.3%

有降级的情况下 QPS 确实比正常集群限流时高不少,但这是降级策略导致的——我们用了保守的单机阈值,没有按节点数均分。如果要更精细地控制,可以在降级时动态计算 threshold / activeNodeCount,前提是控制台能准确感知当前存活的业务节点数。我们评估过,与其引入节点感知的复杂性,不如接受降级期间流量略高,毕竟降级是小概率事件,优先保证可用性。

恢复时的注意事项

Token Server 恢复后,控制台需要把规则切回集群模式。我们在健康检查逻辑里加了恢复动作:连续 3 次探测成功后,重新推送集群规则。但这里有个坑——推送集群规则时,如果业务应用的 Sentinel 客户端还没重新连上 Token Server,它又会触发 Client 端的 fallback 逻辑,可能导致规则切换期间出现短暂的限流失效。

我们的做法是:恢复时先保持本地规则不变,等待 30 秒(给 Client 足够时间重建与 Token Server 的连接),然后再推送集群规则。这个等待时间可以根据实际环境调整,我们压测环境里 30 秒足够,生产环境如果网络复杂可以适当加长。

常见问题

为什么不直接用 Sentinel 客户端自带的 fallback 机制?

Sentinel 客户端的 fallback 到本地规则的前提是本地有规则可 fallback。如果规则是通过控制台动态推送的,客户端本地没有任何持久化的规则文件,fallback 就退化成一个空操作。要让它生效,需要在每个业务应用里维护一份本地规则快照,这在规则频繁变更的场景下运维成本太高。

降级期间如果 Token Server 是假死(比如网络抖动后又恢复),会不会导致规则来回切换?

我们的实现里用了连续失败次数作为判定条件,3 次连续失败才触发降级,3 次连续成功才触发恢复。单次抖动不会导致切换。如果抖动持续时间超过 15 秒(3×5 秒),那它就不是抖动而是真正的故障了,降级是合理的。

为什么降级时不按节点数均分阈值,直接取集群阈值不会放太多流量吗?

按节点数均分的前提是控制台能准确知道当前有多少个业务节点在线。在 Token Server 挂掉的场景下,控制台与业务应用之间的通信可能也不稳定,节点数可能不准。如果低估了节点数,均分后的阈值会过小,导致正常流量被误杀。两害相权,我们选择接受流量偏高,保证业务可用性。如果下游确实扛不住,可以在降级规则里手动设置一个更保守的阈值。

控制台本身会不会成为新的单点?

会。但控制台挂掉的影响和 Token Server 挂掉不同:控制台挂了只是规则无法更新,已经推送到业务应用的规则继续生效;Token Server 挂了是限流直接失效。两者风险等级不一样。如果控制台的高可用也是强需求,可以部署多个控制台实例,用数据库同步规则,但这就是另一个话题了。