本文目录导读:

在 Laravel 中,官方限流器 Illuminate\Cache\RateLimiter 底层使用的是令牌桶算法。
具体原因和对比分析如下:
- Laravel 内置限流器 → 令牌桶算法
- Nginx 限流 → 漏桶算法(
limit_req模块) - Redis + Lua 自定义 → 两种均可实现
为什么 Laravel 选择令牌桶?
令牌桶算法核心特性
- 允许突发流量 (Burst):只要桶内有足够令牌,可以瞬间处理大量请求。
- 平滑限流:令牌以固定速率补充,长期平均速率可控。
Laravel 的 RateLimiter 行为验证(代码逻辑):
// 以“每分钟 10 次”为例
RateLimiter::for('api', function ($job) {
return Limit::perMinute(10);
});
- 前 59 秒未请求:桶内积攒 10 个令牌。
- 第 60 秒瞬间请求 10 次:全部放行(突发)。
- 第 61 秒再次请求:被拒绝(桶已空,令牌未补充)。
这正是令牌桶的典型表现 —— 允许短时间内的突发流量。
漏桶算法的典型表现
- 严格平滑:即使瞬间请求 100 次,也只以固定速率(如 10转/分钟)向外放行,多余的请求直接丢弃或排队。
- 无法利用空闲时间:漏桶的“积水”恒定速度流出,不会因为之前空闲而允许更大的突发。
对比示例(同一场景):
| 时间点 | 请求数 | 令牌桶(Laravel) | 漏桶 |
|---|---|---|---|
| T=0 | 100 | 放行 10 次,拒绝 90 次 | 放行 10 次,拒绝 90 次 |
| T=60(空闲 59 秒) | 100 | 放行 100 次(桶积攒了 100 令牌) | 放行 10 次,拒绝 90 次 |
Laravel 选择令牌桶就是因为 Web 应用需要容忍突发流量(如用户刷新、秒杀开始瞬间)。
如何确认 Laravel 的实现?
可以直接查看 Laravel 源码中 Illuminate\Cache\RateLimiter 的 hit 方法:
public function hit($key, $decaySeconds = 60)
{
$this->cache->add($key, 0, $decaySeconds);
$this->cache->increment($key);
// ... 还有 attempts() 方法读取当前次数
}
- 它基于 Redis + 计数器实现。
- 没有基于时间戳的严格“流出速率”控制,而是通过 固定时间窗口 + 计数重置(
decaySeconds)来模拟令牌的“补充”。 - 这种实现本质上是一种 区间令牌桶(Fixed Window + Burst Allowed)。
什么时候需要考虑换成漏桶?
| 场景 | 推荐算法 |
|---|---|
| API 网关、视频流、下载服务(要求绝对平滑,防止突刺) | 漏桶 |
| 用户操作接口(如发帖、登录)(允许短暂突发,符合人类行为) | 令牌桶(Laravel 默认) |
| 秒杀系统、抢红包(需要精确控制瞬间流量,平滑流出) | 漏桶 + 队列排队 |
在 Laravel 中实现漏桶(如果需要)
Laravel 没有内置漏桶,但可以借助 Redis + Lua 实现:
-- 漏桶 Lua 脚本
local key = KEYS[1]
local leak_rate = tonumber(ARGV[1]) -- 漏水速率 (请求/秒)
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3])
local water = redis.call('get', key)
if water == false then
water = 0
redis.call('set', key, 0)
end
-- 先漏水(根据时间差)
local leak_amount = (now - tonumber(redis.call('ttl', key))) * leak_rate
water = math.max(0, water - leak_amount)
if water < capacity then
redis.call('set', key, water + 1)
return 1 -- 放行
else
return 0 -- 拒绝
end
但这样会丧失 Laravel 内置的优雅语法和 Redis 原子递增优势。
| 令牌桶(Laravel 默认) | 漏桶 | |
|---|---|---|
| 突发容忍 | ✅ 允许 | ❌ 严格平滑 |
| 实现复杂度 | 低(内置) | 高(需自写 Lua) |
| 适用场景 | 通用 Web API、用户操作 | 严格限流、流量整形 |
| 是否推荐 | ✅ 默认首选 | ❌ 除非有明确平滑需求 |
建议: 在 99% 的 Laravel 项目中,直接使用内置的 RateLimiter(令牌桶)即可,只有当你的业务逻辑明确要求“平滑输出”(如视频帧推流、实时数据管道)时,才考虑通过 Redis+Lua 或 Nginx 实现漏桶。