每个严肃的API最终都会告诉你:坐下,安静点。
当你过于急切地请求GitHub、Stripe或AWS,你的请求就会开始收到一个礼貌但坚定的429状态码。这个数字背后,是一个叫“限流器”的组件在默默工作。它决定了一个客户端在给定时间窗口内被允许发出多少请求,保护系统不被压垮,也防止某个贪婪用户抢走所有人的资源。
![]()
简单想法,实现起来却出人意料地棘手。本文作者Maneshwar正在构建git-lrc——一个在每次提交时运行的微型AI代码审查工具,他在开发过程中深入研究了限流器的设计,并决定一步步拆解这个“说不”的系统。
先定义“好”的标准
在写任何代码之前,先明确目标。一个合格的限流器应该满足以下条件:
- 可配置的限制:类似“每个用户每分钟100次请求”的规则,不能写死在代码里,因为免费用户和付费用户理应承受不同的“痛苦”程度。
- 诚实的拒绝:当用户超限时,返回HTTP 429状态码,并附带有用的响应头,告知剩余请求数和窗口重置时间。
- 极低延迟:这个检查在每次请求时都会运行,所以必须快。目标是在P95下低于3毫秒。如果限流器本身很慢,恭喜你,你构建了第二个瓶颈。
- 高可用且共享:多个服务器需要对同一计数达成一致。后面会解释为什么“共享”这个词承担了很重的分量。
第一次尝试:固定窗口计数器
最简单可行的方案是固定窗口计数。把时间切成整齐的一分钟切片,给每个用户一个计数器。每次请求计数器加一,达到限制就被拒绝,下一个窗口开始时计数器重置。
代码逻辑清晰可读,你甚至能跟一只橡皮鸭解释清楚。但问题来了:计数器放在哪里?
第一反应可能是数据库。千万别。那意味着每次请求都要往数据库写一次,你用来保护系统的工具正在悄悄压垮系统本身。数据库方案出局。
那把计数器放在服务器内存里?速度飞快,但前提是你只有一台服务器。一旦流量增长需要横向扩展,问题就来了——不同服务器上的计数器各说各话,用户可能在一台服务器上被限制,在另一台上却畅通无阻。
共享状态的挑战
这正是“共享”一词的关键所在。多个服务器需要就同一组计数达成一致,这意味着计数器必须放在一个所有服务器都能访问的共享存储中。Redis这类内存数据存储成为常见选择,但引入它意味着新的网络延迟和可用性考量。
限流器的设计本质上是在权衡:简单性、性能、准确性和可用性。固定窗口方案简单但存在边界问题——窗口边界处的突发流量可能绕过限制;滑动窗口更精确但实现复杂度上升;令牌桶算法允许一定程度的突发流量,适合处理突发请求的场景。
每种方案都有其适用场景,没有银弹。关键在于理解你的系统流量特征,选择匹配的算法,并在实现时保持对延迟的敏感。
真实世界中的限流器
当限流器真正站在真实流量前面时,它必须足够快、足够准确、足够可靠。3毫秒的P95延迟目标意味着每一次检查都不能拖泥带水。高可用要求意味着限流器本身不能成为单点故障。
构建一个能扛住真实流量的限流器,本质上是在构建一个微型的分布式系统。它需要共享状态、处理网络分区、应对并发竞争,同时保持极低的延迟。这远不止是“数数”那么简单。
从固定窗口到滑动窗口,从单机内存到分布式存储,每一步演进都是对现实约束的妥协与优化。限流器的设计过程,也是理解分布式系统核心挑战的一个缩影。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.