连载中 3/20

滑动窗口:把突刺磨平

2026-07-07 · 4351 阅读 · 0 评论 · 0 赞

病根:格子是死的

上一篇结尾的突刺场景值得再细看一遍:9:59:59.9 到 10:00:00.1,200 毫秒里放行 400 个请求。病根不在计数器,在「格子是死的」——窗口边界是一条免检通道,边界两侧各有一整格的额度,扎堆打在边界上就翻倍。磨平突刺有两条路:要么让统计区间跟着时间滑(任意时刻回看最近 1 秒),要么把死格子切碎、统计最近若干格的总和——两条路都叫滑动窗口,工程上是同一思想的两种精度。

实现一:环形格子,切细了加总

把 1 秒切成 10 个 100 毫秒的小格子,每个格子一个计数器,限流判定时把最近 10 个格子的计数加总,超过阈值就拒绝。格子随时间滚动覆盖,过去的过期出窗,新的滚动进来:

// 环形格子滑动窗口(简化版)
private final long[] buckets = new long[10];     // 10 个 100ms 的格子
private long sum = 0;                            // 窗口内总数
private int head = 0;                            // 最老的格子下标

public synchronized boolean tryAcquire() {
    long now = System.currentTimeMillis();
    roll(now);                                    // 把过期格子的计数清出窗口
    if (sum >= LIMIT) return false;               // 最近 1 秒总量超阈值
    buckets[(int) ((now / 100) % 10)]++;          // 落进当前格子
    sum++;
    return true;
}

突刺为什么被磨平:边界时刻,上一格只贡献它真实 received 的量(比如 20 个),而不是整格 200 的额度——「跨边界双倍放行」变成了「跨边界按实际量累加」。误差从最坏 2 倍缩到最坏 1.1 倍(一个格子的占比)。格子越细,越平滑、越精确,内存与加总成本越高——1 秒 10 格是常用折中,Sentinel 内部的统计结构正是这套环形格子的精致版。

实现二:ZSET 记时间戳,精确但贵

多实例场景要在 Redis 里做滑动窗口,最常见的写法用 ZSET:每个请求记一个成员(时间戳),判定时先清掉窗口外的旧成员,再数剩余个数:

-- ZSET 滑动窗口(Lua 原子执行)
local key = KEYS[1]                 -- 限流标识
local now = tonumber(ARGV[1])       -- 当前毫秒
local win = tonumber(ARGV[2])       -- 窗口毫秒
local limit = tonumber(ARGV[3])
redis.call("ZREMRANGEBYSCORE", key, 0, now - win)   -- 清出窗成员
if redis.call("ZCARD", key) >= limit then
    return 0                                          -- 超阈值
end
redis.call("ZADD", key, now, now .. "-" .. math.random())  -- 记录本次
redis.call("PEXPIRE", key, win)
return 1

这是真·滑动窗口:任意时刻的判定都是「最近 N 毫秒的真实请求数」,零误差。代价也直白——每个请求一条 ZSET 成员,内存消耗与流量成正比,万级 QPS 的接口上这是百万级成员的常驻内存,还得靠 PEXPIRE 兜底清理。所以 ZSET 版适合低频高价值场景(支付接口、短信发送),高频接口老实用环形格子或下一篇的令牌桶。

两种精度的取舍

方案精度内存适用
固定窗口最坏 2 倍突刺一个计数器,极低粗粒度防线,网关初步拦截
环形格子滑动窗口误差为一个格子占比格子数 × 8 字节,低单机高频限流、指标统计
ZSET 时间戳窗口零误差与请求数成正比,高低频高价值接口的分布式限流

滑动窗口解决的是「统计得准」,但它的内心戏是回顾历史:数过去一秒来了多少,超了就拒。还有一种思路根本不数历史,而是控制发放节奏——像发号施令一样,按恒定速率发通行证。这就是下一篇的主角:漏桶与令牌桶,限流算法家族里最经典的两位。

503

10 年全栈工程师 · 503咖啡馆主理人

#滑动窗口#环形格子#ZSET#临界突刺#限流精度

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞