必须用令牌桶实现平滑限流——通过redis+lua封装tokenbucket类,设容量≤1000、速率动态补发,配合seckillratelimit中间件校验sku限流,压测验证200/429码比例符合预期。

电商大促秒杀场景下,瞬时流量可能冲垮接口,必须用令牌桶实现平滑限流——不是简单拒绝请求,而是让有令牌的用户快速通过,没令牌的排队等待或降级,既保系统稳定又不伤用户体验。
准备Redis与环境校验
确认Redis服务已启用且PHP扩展支持:运行php -m | grep redis,输出含redis即通过。若无,请先安装php-redis扩展并重启PHP-FPM。
检查.env中REDIS配置完整:【REDIS_HOST、REDIS_PORT、REDIS_PASSWORD必须非空】,否则后续所有令牌操作将静默失败。
执行php artisan tinker,输入Redis::ping()返回"PONG",表示连接就绪。
封装高并发安全的令牌桶类
创建app/Models/TokenBucket.php:
定义__construct($key, $capacity = 30, $rate = 0.5),其中$key为唯一标识(如"seckill:sku:1001"),【$capacity必须≤1000,否则Redis LIST长度超限触发性能抖动】。
在refill()方法中,用Redis::eval()执行Lua脚本一次性完成“读取当前令牌数+计算应补数量+更新时间戳+写回”,避免多线程竞争导致令牌重复发放。
关键逻辑:若上次填充时间距今超过1秒,按$rate × 秒数补发,但上限不超过$capacity;若不足1秒,跳过填充——这一步省掉毫秒级精度计算,降低CPU开销。
编写秒杀专用限流中间件
运行php artisan make:middleware SeckillRateLimit,编辑app/Http/Middleware/SeckillRateLimit.php:
第一步:从请求头提取X-SKU-ID,拼接为Redis key:$key = 'seckill:sku:' . $request->header('X-SKU-ID');
第二步:实例化TokenBucket,传入$key、容量50、速率2.0(即每秒补2个);
第三步:调用$bucket->tryAcquire(1),返回true则return $next($request);返回false则abort(429, ['message' => '抢购太火爆,请稍后再试'])。
注意:不要在中间件里捕获异常,让Laravel默认错误处理器统一处理,否则掩盖Redis连接失败等底层问题。
路由绑定与压测验证
在routes/api.php中为秒杀接口添加中间件:Route::post('/seckill', [SeckillController::class, 'handle'])->middleware(SeckillRateLimit::class);
使用ab -n 1000 -c 200 http://localhost/api/seckill发起压测,观察Redis中KEYS seckill:sku:*的LEN值是否在0~50间波动,且响应状态码分布为:约80% 200、20% 429——说明令牌桶正在按设定速率平滑放行。
手动向Redis执行LPUSH seckill:sku:1001 token十次,再发起请求,会发现前10次秒过,第11次开始受速率限制——验证了突发流量容纳能力。











