秒杀令牌桶算法详解
📌 一、问题背景
在高并发秒杀系统中,核心矛盾不是“如何下单”,而是如何控制入口流量。如果不做任何限制,所有请求会直接冲击库存服务和数据库,导致系统崩溃。
因此引入“秒杀令牌机制(Token Bucket / Token Gate)”,本质是:
通过提前分配有限令牌,实现流量准入控制
典型场景:
- 10万用户抢100件商品
- 不能让所有请求进入数据库层
- 必须在入口层过滤掉99%以上请求
🎯 二、核心原理
秒杀令牌的核心思想是:只有持有合法令牌的用户才允许进入下单流程。
整体流程分为三层控制:
- 流量入口控制(令牌发放)
- 请求资格校验(令牌验证)
- 业务执行层(库存扣减 + 下单)
核心原则:
- 令牌数量 = 可承载流量上限
- 令牌必须唯一且不可伪造
- 令牌校验必须基于 Redis 原子操作
📊 三、数据结构设计
| Key | 类型 | 作用 |
|---|---|---|
| promo_token_pool_{id} | SET | 令牌池 |
| promo_door_count_{id} | STRING | 大闸控制流量 |
| user_token_{userId} | STRING | 用户持有令牌 |
⚙️ 四、算法分析
秒杀令牌算法本质是一个“有限资源分配问题”:
- 初始化令牌池(根据库存 × 倍数)
- 用户请求时随机/抢占令牌
- Redis 原子校验令牌合法性
- 成功则进入下单流程
关键点:必须保证并发下不会出现重复令牌或超发令牌。
🔄 五、执行流程
系统初始化 → Redis生成令牌池
↓
用户请求进入秒杀接口
↓
获取/校验秒杀令牌
↓
校验失败 → 直接拒绝
↓
校验成功 → 进入库存扣减
↓
MQ异步创建订单
关键点:令牌校验是第一道防线。
📌 六、代码实现(核心)
1. 初始化令牌池
一般是在秒杀活动开始之前的“预热阶段”完成的,核心原则是:越接近活动开始越好,但必须在流量进来之前完成。
| RedisTemplate.opsForSet().add("promo_token_pool_" + promoId, token); |
2. 生成秒杀令牌(大闸控制)
| redisTemplate.opsForValue().set("promo_door_count_" + promoId, stock * 5); |
3. 用户获取令牌
| String token = redisTemplate.opsForSet().pop("promo_token_pool_" + promoId); |
4. 令牌校验
| Boolean exist = redisTemplate.opsForSet().isMember("promo_token_pool_" + promoId, token); |
5. 请求完整流程
用户登录 ↓ 点击秒杀(恶意用户:验证码防刷、nginx对异常IP拉黑) ↓ 获取Token (token生成策略,uuid就可以) ↓ Redis保存Token ↓ 返回前端 ↓ 用户下单 ↓ 携带Token ↓ 校验Token ↓ 删除Token ↓ 扣Redis库存 (就会遇到一系列redis问题,redis的面试题都可能在这里问到) ↓ 发送MQ (这一步可能会存在:redis扣库存成功,但是生成订单失败的情况) ↓ 生成订单 ↓ 扣减MySQL库存 |
⚠️ 七、优缺点分析
| 优点 | 缺点 |
|---|---|
| 有效削峰 | 需要预热令牌池 |
| 保护数据库 | 令牌设计复杂 |
| 高并发可控 | 存在资源浪费 |
❓ 八、面试常见问题
- 秒杀令牌如何防止重复使用?
- 令牌池如何初始化?
- 如何避免令牌被提前抢完?
- Redis 如何保证令牌操作原子性?
🚀 九、总结
秒杀令牌机制的本质是流量准入控制,而不是业务逻辑优化。
核心思想:
- 用 Redis 控制入口流量
- 用有限令牌限制请求数量
- 用 MQ 保证异步削峰
一句话总结:秒杀令牌解决的不是“如何下单”,而是“谁能下单”。
下一篇: 无
相关文章
-
接口优化的几种方法
使用那种方法进行接口优化,取决于不同的业务场景,常见的优化方法:
NEW个对象 2024-10-22
-
RocketMQ延迟消息是怎么实现的?如果让你设计一个延迟消息系统你会怎么做?
延迟消息是消息队列系统中非常常见的能力。
NEW个对象 2026-06-09
-
svn的常见操作
1、代码提交 直接commit即可 2、代码回滚 如果代码已经提交,你可以使用 svn merge 来回滚到某个特定的版本。 如果代码没有提交,可以使用 svn revert 来撤销本地修改。
NEW个对象 2025-03-12