4. 流量治理:限流算法、熔断降级与灰度发布

流量治理,说白了就是给系统装上一套「交通管制系统」。没有它,突发流量一来,服务说挂就挂。我在多个高并发项目里吃过亏,后来才明白——治理不是锦上添花,是保命用的。

这一章我们聚焦三个核心手段:限流、熔断降级、灰度发布与蓝绿部署。它们分别解决「流量太猛怎么办」「下游挂了怎么办」「新版本上线怎么稳」的问题。

流量治理三大核心手段 API 网关入口 限流(令牌桶/漏桶) 熔断降级策略 灰度发布与蓝绿部署 突发流量削峰 下游故障隔离 新版本平滑上线 三者配合使用,才能构建高可用的流量治理体系

4.1 限流算法:令牌桶与漏桶

限流是流量治理的第一道防线。我见过太多系统因为没做限流,被一次秒杀活动直接打垮。限流算法里最经典的就是令牌桶和漏桶,它们思路不同,适用场景也不同。

4.1.1 令牌桶算法

令牌桶的思路很简单:系统以固定速率往桶里放令牌,每个请求来的时候必须拿一个令牌才能通过。桶有容量上限,令牌攒多了也存不住。

这样做的好处是——允许一定程度的突发流量。你想想看,如果桶里攒了 100 个令牌,突然来一波请求,可以一口气全放过去。这在处理秒杀开场、热点新闻推送时特别有用。

核心参数:

  • 令牌填充速率:每秒往桶里放多少个令牌,决定了长期平均速率
  • 桶容量:最多能攒多少个令牌,决定了突发流量的大小
  • 每次请求消耗:通常消耗 1 个,也可以根据请求权重调整

我在项目中用过 Guava 的 RateLimiter,它实现的就是令牌桶变种。不过要注意,分布式场景下不能只靠本地令牌桶,得用 Redis 做分布式限流。

// 伪代码:令牌桶限流
public class TokenBucket {
    private long capacity;        // 桶容量
    private long tokens;          // 当前令牌数
    private long lastRefillTime;  // 上次填充时间
    private long refillRate;      // 每秒填充速率

    public boolean tryAcquire() {
        refill(); // 先补充令牌
        if (tokens > 0) {
            tokens--;
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastRefillTime;
        long newTokens = elapsed * refillRate / 1000;
        tokens = Math.min(capacity, tokens + newTokens);
        lastRefillTime = now;
    }
}

我的经验:令牌桶适合大部分业务场景。但要注意——如果桶容量设得太大,突发流量可能把下游打垮。我一般把容量设为「平均 QPS × 2」,既能应对突发,又不至于失控。

4.1.2 漏桶算法

漏桶的思路正好反过来。它不管请求来得有多猛,出去的速率是固定的。就像一个有漏洞的水桶,水进来再多,漏出去的速度也是恒定的。

这样做的好处是——输出流量平滑。不会出现忽高忽低的情况,对下游服务特别友好。

对比维度 令牌桶 漏桶
输出流量 允许突发 平滑
实现复杂度 中等 简单
适用场景 大部分业务 API 数据库写入、消息队列
对下游保护 一般 强

我曾经在一个日志收集系统里用过漏桶。日志写入速率必须稳定,否则数据库扛不住。漏桶把突发的日志写入请求排队,以固定速率刷到数据库,效果很好。

避坑指南:漏桶的缺点是——如果请求持续超过处理能力,队列会无限增长,最终导致超时。我曾经遇到过队列积压到几百万,内存直接爆了。解决方案是给队列设上限,超出的请求直接丢弃或返回 429。

4.2 熔断降级策略

限流管的是「流量太大」,熔断管的是「下游挂了」。你想想看,如果 A 服务调用 B 服务,B 已经响应超时了,A 还在疯狂重试,结果就是 A 的资源也被耗尽——这叫「雪崩效应」。

熔断降级的思路很简单:发现下游有问题,就暂时切断调用,快速返回降级结果。等下游恢复了,再慢慢放行请求。

4.2.1 熔断器的三种状态

经典的熔断器有三个状态:关闭、打开、半开。

  • 关闭状态:正常调用,但统计失败率。失败率超过阈值,进入打开状态。
  • 打开状态:直接拒绝请求,快速返回降级结果。持续一段时间后,进入半开状态。
  • 半开状态:放少量请求试探下游。如果成功,关闭熔断器;如果失败,回到打开状态。

我习惯用 Hystrix 或者 Resilience4j 来实现熔断。不过 Hystrix 已经停止维护了,新项目建议用 Resilience4j。

// 伪代码:熔断器核心逻辑
public class CircuitBreaker {
    private State state = State.CLOSED;
    private int failureCount = 0;
    private int failureThreshold = 5;   // 5次失败触发熔断
    private long lastFailureTime;
    private long timeout = 10000;       // 10秒后尝试半开

    public boolean allowRequest() {
        if (state == State.OPEN) {
            if (System.currentTimeMillis() - lastFailureTime > timeout) {
                state = State.HALF_OPEN;
                return true;
            }
            return false;
        }
        return true;
    }

    public void recordFailure() {
        failureCount++;
        if (failureCount >= failureThreshold) {
            state = State.OPEN;
            lastFailureTime = System.currentTimeMillis();
        }
    }

    public void recordSuccess() {
        if (state == State.HALF_OPEN) {
            state = State.CLOSED;
            failureCount = 0;
        }
    }

    enum State { CLOSED, OPEN, HALF_OPEN }
}

降级策略示例:

  • 返回缓存数据(比如上次查询结果)
  • 返回默认值(比如空列表、0)
  • 返回错误提示(比如「服务繁忙,请稍后重试」)
  • 异步转同步(比如先返回一个任务 ID,后续轮询)

我的经验:熔断阈值不要设得太死。我一般把失败阈值设为 10 次/10 秒,半开试探请求设为 3 个。太敏感的话,网络抖动就会触发熔断,反而影响可用性。

4.3 灰度发布与蓝绿部署

新版本上线,最怕的就是全量发布后出问题。灰度发布和蓝绿部署就是用来解决这个问题的——让新版本先在小范围验证,没问题再全量推。

4.3.1 灰度发布

灰度发布的核心是「按比例引流」。比如先让 5% 的流量走新版本,观察一段时间没问题,再逐步提高到 10%、50%,最后全量。

在 API 网关里做灰度,通常基于请求特征来路由:

  • 用户 ID 哈希:按用户 ID 取模,决定走哪个版本
  • 请求头标记:比如内部测试人员带一个 X-Canary: true 头
  • 地域/设备:先让某个地域或某类设备走新版本
// 伪代码:灰度路由逻辑
public class GrayRouter {
    private static final int GRAY_PERCENT = 5; // 5% 灰度

    public String route(String userId) {
        int hash = Math.abs(userId.hashCode()) % 100;
        if (hash < GRAY_PERCENT) {
            return "new-version";
        }
        return "old-version";
    }
}

避坑指南:我曾经遇到过灰度比例设成 1%,结果用户量太大,新版本还是被冲垮了。灰度发布一定要配合限流——新版本的限流阈值要设得保守一些,防止小流量也扛不住。

4.3.2 蓝绿部署

蓝绿部署的思路更简单:准备两套完全一样的环境——蓝环境和绿环境。当前只有一套在提供服务,另一套闲置。新版本部署到闲置环境,测试通过后,把流量全部切过去。

对比维度 灰度发布 蓝绿部署
资源消耗 低(共用环境) 高(双倍环境)
回滚速度 慢(逐步调整比例) 快(直接切回旧环境)
验证粒度 细(可按用户/特征) 粗(全量切换)
适用场景 功能验证、A/B 测试 版本升级、紧急回滚

我个人习惯:核心服务用蓝绿部署,因为回滚快,出问题能秒级恢复。非核心服务用灰度发布,省资源,还能做 A/B 测试。

网关层面的灰度实现要点:

  • 网关需要维护「版本路由表」,动态更新
  • 灰度策略要支持热更新,不能重启网关
  • 灰度流量要打上标签,方便监控和排障
  • 灰度期间要实时监控错误率和响应时间,异常自动熔断

嗯,流量治理这块内容不少,但核心就这三板斧。限流管入口,熔断管依赖,灰度管发布。把它们在网关层落地好,系统的稳定性就能上一个台阶。