速率限制器

海龙2025-5-20编程架构实验

速率限制器

在网络系统中,**速率限制器(Rate Limiter)**用于控制客户端或服务端的请求速率,防止资源滥用、降低系统负载并提高稳定性。例如:

  • 限制某 IP 每分钟最多发送 10 篇博客。
  • 每用户每天领取优惠券上限为 10 次。
  • 短信验证码每 60 秒发送一次。

限流的好处包括防止 DoS 攻击、保护服务端资源、降低运营成本等。本文将深入探讨速率限制器的设计要求、类型、实现方式以及在单机和分布式环境下的差异。

速率限制器的核心要求

一个优秀的速率限制器需满足以下要求:

  • 精准性:准确记录和计算请求速率,不漏计任何请求。
  • 低延迟:尽量不增加请求响应时间。
  • 低资源占用:减少对 CPU、内存等资源的消耗。
  • 用户通知:通过 HTTP 状态码(如 429 Too Many Requests)告知客户端限制情况,并提供相关信息(如剩余请求数)。
  • 容错性:即使限流器故障,也不应影响系统整体运行。

速率限制器的类型

1. 客户端限流

在客户端实现限流逻辑,开发成本低,但安全性较差:

  • 客户端可轻易绕过限制(如修改代码)。
  • 多客户端场景下,各客户端独立计数,无法统一管理。

适用场景:对安全性要求低的场景,或作为服务端限流的补充。
实现方案

  • 重写 fetch 和 XMLHttpRequest:通过改写浏览器原生的 fetch 和 XMLHttpRequest 方法,在每次发起 HTTP 请求前插入限流逻辑
  • 使用 Service Worker:Service Worker 作为浏览器和网络之间的代理,可拦截所有符合其作用域的网络请求,包括 fetch 请求、页面导航、资源加载等。关于 Service Worker 的更多内容,可以参考我的另一篇文章open in new window

2. 服务端限流

服务端限流更安全,常见算法包括:

  • 令牌桶(Token Bucket):以固定速率向桶中添加令牌,请求需消耗令牌,适用于突发流量场景。
  • 漏桶(Leaky Bucket):请求以固定速率流出,超出部分被丢弃,适合平滑流量。
  • 固定窗口计数器:在固定时间窗口内计数,简单但可能导致窗口边界突刺问题。
  • 滑动窗口:通过记录请求时间戳实现更细粒度的控制,适合高精度场景。

实现示例(以 Redis 和滑动窗口为例):

-- Redis Lua 脚本:滑动窗口限流
local key = KEYS[1] -- 用户标识
local window = tonumber(ARGV[1]) -- 时间窗口(秒)
local limit = tonumber(ARGV[2]) -- 请求上限
local now = tonumber(ARGV[3]) -- 当前时间戳

-- 删除窗口外的旧请求
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 获取当前窗口内的请求数
local count = redis.call('ZCARD', key)
if count < limit then
    -- 添加新请求时间戳
    redis.call('ZADD', key, now, now)
    return 1 -- 允许请求
else
    return 0 -- 拒绝请求
end

3. API 网关限流

云服务中的 API 网关(如 AWS API Gateway、Nginx)通常内置限流功能,支持速率限制、SSL 终止、IP 白名单等。配置简单,但灵活性较低,适合需求不复杂的场景。

推荐场景:中小型项目或已有 API 网关的项目。

HTTP 请求的限流配置

当请求被限制时,服务端应返回 429 Too Many Requests 状态码,并在响应头中提供以下信息:

  • X-RateLimit-Limit:允许的最大请求数(如 100)。
  • X-RateLimit-Remaining:当前剩余请求数(如 10)。
  • X-RateLimit-Reset:下次重置的 Unix 时间戳(秒,如 1726339200)。
  • X-RateLimit-Retry-After:建议等待时间(秒,如 10)。

单机 vs 分布式限流

单机限流

在单服务器环境中,限流实现较为简单:

  1. 定义限流规则,存储在磁盘中,工作时,需要从磁盘上读取规则,然后存储在某个缓存中使用。
  2. 请求到达时,限流器从缓存加载规则,依据算法判断是否允许。
  3. 若被限制,返回 429 状态码;否则放行至业务逻辑。

优点:实现简单,适合小型系统。
缺点:无法应对分布式场景下的高并发。

分布式限流

分布式环境可以容许更大用户量,但是需解决以下问题:

  • 竞争问题:多请求并发修改计数器,可能导致计数不准。
    • 解决方案
      • 使用 Redis 的 INCR 命令(原子性操作)。
      • 使用 Redis Lua 脚本保证操作原子性。
      • 使用 Redis Bitmap 记录请求时间戳,适合高精度场景。
  • 同步问题:多个限流器独立计数,无法统一管理。
    • 解决方案
      • 集中存储:使用 Redis 或 ZooKeeper 存储计数数据。
      • 一致性哈希:将同一客户端请求分配到固定节点,减少同步开销。
      • 分布式锁:通过 Redis 或 ZooKeeper 实现全局锁,但需权衡性能。

总结

速率限制器是构建高可用系统的关键组件。单机限流实现简单,适合小型应用;分布式限流需解决竞争和同步问题,推荐使用 Redis 等集中存储方案。选择合适的限流算法(如令牌桶、滑动窗口)并结合 API 网关或自定义实现,可以满足不同场景的需求。

最后更新日期 5/21/2025, 2:35:45 AM