单机限流和集群限流的区别是什么?

#中间件#面试八股

单机限流和集群限流的区别是什么?

结论:单机限流(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」失败导致的长久残留。

小结

  • 单机限流简单、快,但全局阈值随实例数漂移;集群限流精确,但多一跳、有单点。
  • 生产常用「入口集群 + 实例单机」两层,阈值按比例配置。
  • 任何集群限流方案都必须先定义存储故障时的降级策略。
笔记加载中…