一个抽奖系统,真正难的不是随机,而是把这4件事拆对

抽奖系统最容易写的是随机数。难的,是不重复、不断货、不中改、奖品可追溯。
先把这4件事处理好,系统才能真正稳定。

四分离框架

不少人以为抽奖难就只在算法:

  • 随机数怎么写
  • 权重怎么算

但真实线上问题很少是“随机”本身,更多是这4类事故:

  1. 用户反复点,重复中奖
  2. 两个人并发抢最后一份库存,系统超卖
  3. 一套玩法下要维护多套抽奖逻辑
  4. 中奖后没发到奖,或核销后没法追溯

这篇给你一份可以直接用于一版产品的抽奖架构:先稳住主链路,再逐步扩展。

先说结论:第一版先稳住主链路

抽奖助手首版不先上 Redis / MQ / 调度器,直接用 MySQL 把一致性边界打牢。

  • 先把幂等、库存和结果落地做对
  • 先把发奖、核销打通并可追溯
  • 有了稳定主链路后再考虑异步化

这样能最快确认最核心问题是否正确,不会把复杂度扩散到很多系统里。

这4件事,怎么拆

1. 规则与表现分离:动画只能展示,结果只能由服务端决定

服务端先返回唯一 draw_option_code,前端只负责展示对应结果。

同一个抽奖活动可以有多种展现(大转盘、随机卡片、赛跑),但背后只走一套抽奖判定。

展现配置解析顺序也要固定:

  1. 有来源绑定配置:用来源配置
  2. 否则用默认配置
  3. 都不可用则拒绝访问

这条规则可以避免“页面换了就改结果逻辑”的风险。

2. 奖项与奖品分离:玩法和履约不能混一起

抽奖选项和真实奖品必须分开。

  • lottery_draw_option:记录抽中了什么(一等奖、二等奖、未中奖)
  • reward_definition:记录发什么(券、实物、虚拟权益)

这样一张券能被不同活动复用,但每个抽奖选项仍有独立库存,业务就不会互相污染。

3. 参与、结果、发奖、核销分离:每张表只回答一类问题

  • marketing_participation:一次参与行为(含 requestId 幂等)
  • lottery_draw_record:本次命中的选项
  • reward_issue_order:发奖流程到哪一步
  • verification_voucher + verification_record:核销状态与动作

特别说明:

  • participant_user_code 是原始参与人
  • claim_user_code 是领奖人

参与人绑定主账号后,历史参与不能重算,只能改变奖品承接关系。

4. 库存与流水分离:一份库存表,一张流水表

  • reward_inventory:当前余额,决定能不能发奖
  • reward_inventory_flow:每次变动记录,便于追溯

关键原则:

  • 扣减前先做条件判断并原子更新库存
  • (draw_option_code, business_type, business_code) 做唯一键,防重复影响
  • 结果、库存、流水、发奖同一事务提交,避免半成功状态

抽奖主链路(适合直接放到文章中的流程图说明)

单事务抽奖链路

  1. 用户提交 requestId
  2. 校验幂等,创建参与记录
  3. 服务端计算 draw_option_code
  4. 命中未中奖:仅落结果
  5. 命中奖品:库存条件扣减成功后,落结果、发奖单、流水、凭证
  6. 参与标记为成功
  7. 事务提交后再播放开奖动画

并发保护点:

  • 同一 requestId 只允许一条参与记录
  • 同一库存变动有幂等唯一键
  • 扣减失败时重试其他候选选项,避免“无限重试”

发布时别忘了的边界

  • 抽奖只支持已发布且可用状态的活动与展现配置
  • 活动至少要有一个启用选项,权重必须有效
  • 奖品/库存、发奖和核销状态独立,避免互相覆盖
  • 核销不成功要可逆回退,数据仍保留完整链路

可直接放在文末的验收清单

  • 同一 requestId 不重复抽奖
  • 并发只允许最后库存产生一次发奖
  • 未中奖不创建发奖单和核销凭证
  • 一次中奖只对应一张发奖单
  • 一次核销有唯一可追溯记录
  • 前端样式调整不改动抽奖判定

技术补充:抽奖助手 ER 图(字段级)

erDiagram
  direction LR
  产品 ||--o{ 选项 : 配置
  选项 ||--|| 库存 : 库存
  选项 ||--o{ 结果 : 结果
  奖品 ||--o{ 选项 : 奖品
  用户 ||--o{ 参与 : 参与
  参与 ||--|| 结果 : 命中
  结果 ||--o| 发奖单 : 发奖
  发奖单 ||--o| 凭证 : 凭证
  凭证 ||--o| 核销 : 核销

一句话收束:抽奖不在于会不会写随机数,而在于边界写得不写对。