第 11 章 · 限流第 3 页,共 9 页
怎样避免窗口交界处的漏洞?
不在固定的时刻清零,而是随时只数从现在往前的 12 秒。可以记下每一次的时间,也可以只用两个数来估算。
为什么需要
上一页的漏洞出在「清零」:一到交界,前面刚放行的就像没发生过。要让任意 12 秒都不超过 8 个,每次都应该只看从现在往前的 12 秒。这种随「现在」一起移动的数法,叫作(sliding window)。
怎样做
第一种做法是记下每一次的时间:每放行一个请求,就记下它的时刻。新的请求到来时,先删掉 12 秒以前的记录,再数剩下几条:不到 8 条就放行并记下这一次,已经有 8 条就拒绝。它数的就是真实的最近 12 秒,不会估错,还能算出最早那条记录多久以后移出,也就是对方要等多久。
第二种做法按比例估算,只用两个数:上一个固定窗口放行了几个,这一个窗口到现在放行了几个。比如现在是第 15 秒,往前 12 秒是第 3–15 秒,其中第 3–12 秒是上一个窗口的 9 秒,占它的 75%。于是估计值 = 上一个窗口的个数 × 75% + 这一个窗口的个数。估计值小于 8 就放行,并给这一个窗口加一。
在下图里切换三种数法。选中一个请求,图上会画出限流器在它到达时看的是哪一段时间。
看哪种数法补上了交界的漏洞
先看这里选「挤在交界前后」,在三种数法之间切换,看读数「任意 12 秒里最多放行」。切到「按比例估算」时,图下列出它估错的请求;再选「挤在窗口开头」,看另一种估错。
- 放行 18 个
已选:请求 1 · 第 1 秒 · 放行
用左右方向键选择一项;按住 Shift 再按左右方向键,或者用「提前一格」「推后一格」按钮,移动选中的一项。
所有标记都是同一个请求者发出的请求;时刻挨得太近的,标记上下错开画。
请求 1 在第 1 秒到达:往前 12 秒(第 0–1 秒)里已经放行了 0 个,不到 8 个,放行,并记下这一次。
代价是什么
记下每一次的时间把漏洞完全补上,按比例估算补上了大半,代价不同。前者最准,要存的东西却随上限增加:Redis(一种常用来存放计数的软件)的限流教程算过,上限是每小时 10,000 个时,每个请求者最多要存 10,000 条记录(2026)。按比例估算只存两个数,代价是它假设上一个窗口的请求是均匀到达的:请求挤在那个窗口的开头时,它会多估,拒绝本该放行的;挤在末尾时又会少估,多放几个。图里的几组预设是特意挑出来、最容易估错的情况,真实流量里估错要少得多:替网站抵御攻击的 Cloudflare 公司介绍这种办法时也作了同样的假设,并用真实流量检验过,估出的速度与真实速度平均只差 6%(2017)。
滑动窗口也解决不了窗口太长的问题。免费证书服务 Let's Encrypt(证书是网站向浏览器证明自己身份的电子文件)的旧规则大多按一周计数,用完可能要等好几天,它最迟到 2024 年底已改用配额一点点恢复的办法(2025),这就是下一页的思路。
资料来源
以下资料均于 2026-10-19 访问。