单机限流和集群限流的区别是什么?
结论:单机限流(Standalone)每个实例独立计数,实现简单、无额外依赖,但全局阈值等于「单机阈值 × 实例数」,扩缩容与负载不均时都不准;集群限流(Cluster)把计数集中到 Redis 或限流服务,全局精确,代价是多一次网络往返、单点风险与性能瓶颈。生产常见组合是「入口集群限流控总量,实例内单机限流兜底」。
一、对比表
| 维度 | 单机限流 | 集群限流 |
|---|---|---|
| 计数位置 | 进程内(LongAdder、滑动窗口) | Redis 或令牌服务器 |
| 精度 | 全局阈值 ≈ 单机值 × 实例数 | 全局精确 |
| 延迟 | 纳秒级 | 多 0.5 到 2 ms 往返 |
| 扩缩容 | 需改配置或按实例数分摊 | 自动跟随,无需改阈值 |
| 负载不均 | 被打满的实例先拒绝 | 全局统一判定 |
| 外部依赖 | 无 | Redis 或限流服务的可用性 |
| 故障影响 | 无 | 存储故障需降级到本地 |
二、全局阈值怎么算
单机限流(每台 250) 集群限流(全局 1000)
┌────┐┌────┐┌────┐┌────┐ ┌────┐┌────┐┌────┐┌────┐
│250 ││250 ││250 ││250 │ │ ? ││ ? ││ ? ││ ? │
└────┘└────┘└────┘└────┘ └──┬─┘└──┬─┘└──┬─┘└──┬─┘
实际承载取决于 LB 是否均匀 └─────┴─────┴─────┘
某台被打到 400 QPS 就先拒绝 共享计数,合计不超过 1000
负载均衡不均时,单机限流会让热点实例先拒绝,而其他实例还空着。
三、Redis 集群限流实现
-- KEYS[1]=rl:order:2025110312 ARGV[1]=window(ms) ARGV[2]=max
local key = KEYS[1]
local window = tonumber(ARGV[1])
local max = tonumber(ARGV[2])
local cur = redis.call('INCR', key)
if cur == 1 then
redis.call('PEXPIRE', key, window)
end
if cur > max then
return 0
end
return max - cur -- 返回剩余额度,便于上层做阈值自适应
public boolean allow(String resource, int maxPerWindow) {
String key = "rl:" + resource + ":" + bucket(); // bucket() 按窗口取整
try {
Long left = redis.execute(RATE_LIMIT_SCRIPT,
Collections.singletonList(key),
String.valueOf(WINDOW_MS), String.valueOf(maxPerWindow));
return left != null && left >= 0;
} catch (Exception e) {
// Redis 故障:降级为本地限流,宁可少放也不放全量
metrics.counter("ratelimit.redis.fail").increment();
return localLimiter.tryAcquire();
}
}
四、Sentinel 集群流控
# Token Server 模式:一台实例当 token server,其余为 client
cluster:
mode: server # 或 client
server-host: 10.0.0.20
server-port: 18730
request-timeout-ms: 50
flow:
- resource: orderCreate
grade: 1
count: 1000
cluster-mode: true
cluster-threshold-type: GLOBAL
Token Server 承担集中计数,client 每次请求前申请令牌。request-timeout-ms 要设小,超时按失败处理并降级到本地,否则 token server 抖动会拖慢整条链路。
五、三种折中方案
- 配额租借:中心按实例数下发配额,实例本地消耗、定期续租。精度略降,但去掉每请求的网络开销。
- 分片限流:按 client 哈希到多个 token server,避免单点瓶颈。
- 分层限流:网关集群限流控总入口,服务实例单机限流防过载,两层阈值按 8:10 配置,留出缓冲。
六、常见追问
Q1:Redis 挂了怎么办? 必须降级到本地限流,阈值取「全局阈值 / 实例数 × 1.2」。宁可整体略微超发,也不能完全放开。
Q2:集群限流会不会超发?
会。一个网络往返内可能有多个请求同时通过,超发量约等于 QPS × RTT。RTT 1 ms、QPS 1 万时约 10 个,通常可接受。
Q3:什么时候只用单机限流? 实例数少且稳定、流量均匀、对总量精度要求不高的内部服务。对外 API 与计费类接口必须用集群限流。
Q4:集群限流的计数为什么要设过期?
否则窗口 key 会无限增长。用 PEXPIRE 保证 key 在窗口结束后自动清理,同时避免「先 INCR 后 EXPIRE」失败导致的长久残留。
小结
- 单机限流简单、快,但全局阈值随实例数漂移;集群限流精确,但多一跳、有单点。
- 生产常用「入口集群 + 实例单机」两层,阈值按比例配置。
- 任何集群限流方案都必须先定义存储故障时的降级策略。