在电商大促(如双黑、618)场景下,优惠券系统是典型的**“高并发、高一致性、业务逻辑复杂”的重灾区。作为资深 Java 开发,应对大促不能只靠加机器,必须从架构设计、性能优化、资深兜底**三个层面进行全方位“武装”。
以下是应对大促的实战策略:
1. 领券阶段:高并发下的流量削峰
领券是瞬时压力最大的环节。
-
异步化处理 (MQ):
-
策略:用户点“领取”时,后端只做基础校验(是否已领完、是否在有效期),然后直接发个消息到 MQ,给用户返回“领取中”。
-
落地:后台异步消费消息进行实际的落库和发券。这样可以极大地保护数据库。
-
-
缓存预热与扣减:
-
做法:大促前将优惠券总量加载到 Redis。利用 Redis 的原子操作(如
DECR或 Lua 脚本) 进行预扣减。 -
风险防范:Redis 扣减成功才代表领到,防止数据库在高并发下被撑爆。
-
2. 核券阶段:分布式事务与一致性
下单核券是整个链路中最核心的环节,必须保证“券不能多用”且“不能误扣”。
-
状态机与幂等性:
-
设计:优惠券状态必须有严格的状态机(未使用 -> 占用中 -> 已使用 -> 已过期)。
-
幂等:核券接口必须支持幂等(利用订单号做唯一键),防止因为网络超时重试导致一张券被核销两次。
-
-
预占与释放 (TCC 模式):
-
流程:下单时先“锁定(占用)”优惠券,等用户真正支付成功后再“核销”。
-
补偿机制:如果订单取消或支付超时,必须通过定时任务或 MQ 延迟队列及时“释放”优惠券,让用户可以再次使用。
-
3. 查询阶段:读写分离与多级缓存
大促期间,用户会频繁打开“我的优惠券”列表,这会产生海量的查询请求。
-
多级缓存策略:
-
L1 (本地缓存):使用 Caffeine 存储一些不经常变动的券元数据(如券的名称、背景图、规则说明)。
-
L2 (分布式缓存):使用 Redis 存储用户的持有券列表。
-
-
索引优化:确保数据库中
user_id和status是复合索引。由于大促期间写压力大,建议开启数据库的读写分离,查询请求全部走从库。
4. 资深开发的“保命”手段 (稳定性)
-
限流与熔断:
-
如果发券系统响应变慢,利用 Sentinel 直接触发熔断,优先保证下单主链路,领券功能可以暂时关闭。
-
-
大 Key 拆分:
-
不要把一个大促活动的所有券 ID 都存在一个 Redis List 里。按
user_id进行 Hash 散列,防止产生 Redis 热 Key。
-
-
防羊毛/防刷:
-
对接风控系统。在领券接口前置 布隆过滤器 或 IP 限流,拦截掉那些职业“羊毛党”的脚本请求。
-
🛠 给转型者的思考
如果你之后转型做产品经理或运营,优惠券的设计就不仅是技术问题了:
-
门槛设计:为什么优惠券要设置 105 减 10 而不是直接减 10?(为了提升客单价)。
-
库存控制:大促期间如果券发多了导致亏本,产品上是否有“紧急撤回”或“阶梯发券”的策略?
📉 状态同步
-
2026-03-30:已完成优惠券大促应对方案(异步领券、TCC核券、多级缓存及防刷策略)的深度解析。
既然聊到了大促,你想看看如何编写一段 Lua 脚本来实现“Redis 原子预扣减优惠券库存”吗?这是大促系统中最核心的一段代码。