限流到底在限制什么?

聊限流之前,我先问个问题:你的水印接口平时扛多少流量?我见过很多团队,平时一天几千请求,突然搞个活动,几万请求涌过来,服务直接挂了。嗯,这就是典型的「没限流」。说白了,限流就是主动控制流量,不让系统被冲垮。

限流的本质定义

限流,全称「流量限制」(Rate Limiting)。它做的事情很简单:在单位时间内,只允许指定数量的请求通过,超出的请求直接拒绝或排队。 你看,这不是什么高深理论,就是给系统装一个「水龙头」。

我之前接手过一个老旧水印服务,并发处理能力只有 200 QPS。上线后经常半夜被报警吵醒——后来发现是爬虫在批量刷接口。加上限流后,系统稳定多了。说白了,限流不是为了限制用户,而是保护你自己的服务。

两种常见维度:QPS 和并发数

限流到底从哪几个角度看?我个人习惯按两种维度来划分:QPS(每秒请求数) 和 并发数(Concurrency)。很多人傻傻分不清,我画个表讲清楚:

维度定义典型场景我的踩坑经验
QPS每秒能处理多少请求API 网关、登录接口偶尔突增 10 倍请求,QPS 限流直接拦住
并发数同时正在处理的请求数数据库连接池、线程池并发数超过 50 时,RT 会飙升到秒级
关键区别: QPS 看的是「速率」,并发数看的是「并行量」。举个例子:100 QPS 但每个请求处理 1ms,并发数只有 0.1;同样 100 QPS 但每个请求处理 1s,并发数就是 100。两者混用会出大问题。

QPS 限流的实际配置

我习惯在代码里用 @RateLimiter 来控制 QPS。以 Java 为例,Sentinel 的规则配置很直观:

// 设置水印接口的 QPS 上限为 500
FlowRule rule = new FlowRule();
rule.setResource("watermark_add");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(500);
FlowRuleManager.loadRules(Collections.singletonList(rule));

注意这里的「count」是每秒允许通过的最大请求数。超过 500 的请求会直接返回降级结果(比如「请稍后重试」)。要是你把 count 设成 5000,而服务器扛不住 5000 QPS……嗯,你猜怎么着?服务照样挂。所以配置前先压测。

并发数限流的典型配置

并发数维度更适合控制线程池或连接池级别的资源。比如你有个文字水印渲染服务,底层用了 20 个线程池线程:

// 限制并发数不超过 20
FlowRule rule = new FlowRule();
rule.setResource("text_watermark_render");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule.setCount(20);
FlowRuleManager.loadRules(Collections.singletonList(rule));

一旦并发数达到 20,新的请求会排队等待,而不是直接把线程池撑爆。我曾在项目里遇到过并发数配置过高(设了 100),结果线程池耗尽导致线程阻塞,整个服务无法响应其他接口。后来我把并发数压到 30,问题就解决了。

限流与流量整形:不是一回事,但有关系

流量整形(Traffic Shaping)跟限流经常被混淆。我举个例子帮你区分:

  • 限流: 堵住多余的水。超出部分直接扔掉或拒绝。
  • 流量整形: 把突发的水流变成平滑的涓涓细流。允许等待,但不丢请求。

你想想看,如果水印接口接受 500 QPS,但客户端突然在 1 秒内发了 2000 个请求。限流会直接丢掉 1500 个;而流量整形可能让这 2000 个请求在 4 秒内慢慢处理完,每个请求只延迟几毫秒。哪种更好?

我个人习惯在核心接口上用「令牌桶」做流量整形,既保护系统,又不让用户感到明显的拒绝。看这段伪代码:

// 令牌桶:每 100ms 生成 50 个令牌,桶容量 500
rate = 500;               // 每秒 500 个令牌
capacity = 500;           // 最大突发 500 个请求
tokens = 500;             // 初始满桶
last_refill_time = now();

function try_acquire(count):
    refill_tokens()
    if tokens >= count:
        tokens -= count
        return true
    else:
        return false   // 等待下一批令牌
小技巧: 我用令牌桶时,会把桶容量设得比平均速率略大(比如 500 QPS 配 600 容量),这样短时突发也能平滑处理,不至于频繁触发降级。

限流维度怎么选?一张图说清楚

为了帮你直观理解 QPS 与并发数的区别,以及限流和流量整形的关系,我画了张结构图:

限流核心概念分层图 限流定义:控制单位时间流量 QPS限流:每秒请求数 并发数限流:同时处理数 场景:API网关、登录接口 场景:线程池、连接池 流量整形:平滑突发,不丢请求 关键区别 限流是“挡”,流量整形是“匀”。两者常常配合使用,先整形再限流。

限流的避坑指南

我自己踩过不少坑,简单列几条最常见的:

  • 别把 QPS 和并发数搞混。 我曾经把 QPS 1000 当成并发数 1000,结果线程池直接炸了。你看,两个数字一样,但意义完全不同。
  • 限流要配合降级。 光限流不降级,用户看到内部错误会骂人。返回友好的提示(比如“系统繁忙,请稍后”)才是正道。
  • 流量整形别设得太松。 我试过桶容量设成平均速率的 5 倍,结果突发流量一来,后端的慢查询直接把数据库拖垮了。建议桶容量不超过平均速率的 1.5~2 倍。
  • 分布式环境要共享状态。 单机限流在集群下会失效,必须用 Redis 或 Sentinel 的集群限流模式。
重要提示: 限流配置不是一次搞定就完事。上线后持续监控实际流量,动态调整阈值。我曾经在配置中心留了个接口,每个小时自动根据前 10 分钟的峰值调整限流值,效果很不错。

总结本章核心点

限流就是给系统装个刹车,QPS 和并发数是两个不同的刹车踏板。流量整形则是让刹车更平顺的缓冲器。你想想看,如果一个水印接口没限流,就像跑车没刹车——看起来很猛,但早晚要出事。