接到大转盘需求,先想清楚奖品怎么"发完就没了"
接到大转盘需求,先想清楚奖品怎么"发完就没了"
大转盘、刮刮乐这类活动,需求描述通常是:iPhone 概率 1%,优惠券概率 99%。
看起来很简单,配个概率表就行。但发上线才发现:iPhone 只备了 10 台,活动跑了一天,发出去 47 台。
只配概率,控不住总量
概率决定的是频率,不是总量——这个问题在集卡文章里也提过。
1% 的概率 × 5000 次抽奖 = 期望 50 台。备货只有 10 台,剩下 40 台发的是什么?发的是系统 bug。
正确的设计是:奖品池 + 兜底奖。
奖品池:提前把奖品放进去
活动开始前,把所有实物奖品预写进 Redis List:
# 活动初始化:10 台 iPhone 入池
for i in range(10):
redis.rpush("prize_pool:iphone", f"sn_{i:04d}")
抽奖时,先用概率算"这次应该中什么奖",再去池子里取:
def draw(user_id: str) -> str:
prize_type = weighted_random(prize_config) # 按概率选奖品类型
if prize_type == "iphone":
sn = redis.rpop("prize_pool:iphone") # 原子取一个
if sn:
record_prize(user_id, "iphone", sn)
return f"恭喜获得 iPhone!序列号 {sn}"
else:
# 池子空了,降级到兜底奖
prize_type = "coupon"
coupon = issue_coupon(user_id)
record_prize(user_id, "coupon", coupon)
return "获得 5 元优惠券"
RPOP 是原子操作,并发下不会重复发同一个序列号。池子空了就降级,不会超发。
兜底奖:实物送完之后发什么
这个问题很容易忘记设计。
常见方案是配一个"保底奖"(积分、小面额优惠券),实物奖品池取空后自动切换过去。用户看到的转盘结果可以是"非常遗憾,iPhone 已被抢完,送你 10 积分安慰一下",也可以不提实物售罄,直接出优惠券结果。具体看产品决策。
兜底奖本身通常不需要放进池子(数量不限),配置一个固定发放逻辑就行。
奖品概率要动态调整吗
活动前期和后期,奖品剩余数量差异很大,是否需要根据剩余量动态调整概率?
一般不需要——实物券池子控总量,概率只控制"命中实物奖品入口"的频率,命中了但取不到就降级,效果一样。强行动态调整概率反而会让概率计算变复杂,而且用户感知不到。
有一个例外:活动快结束时,剩余实物奖品很少,需要人工下线奖品类型(把 iPhone 从概率表中删掉),防止用户看到 iPhone 但永远取不到。这是运营操作,代码上留个开关就行。
发奖记录必须幂等
转盘动画和发奖是异步的,网络抖动下用户可能多次触发抽奖接口。
每次抽奖生成一个 requestId,发奖记录表用 requestId 做唯一索引:
CREATE TABLE prize_record (
request_id VARCHAR(64) PRIMARY KEY,
user_id BIGINT,
prize_type VARCHAR(32),
prize_value VARCHAR(64),
created_at DATETIME
);
接口进来先查记录,有就直接返回上次结果,不重复发奖。
上线前必须确认的两件事
- 奖品池已初始化:入池操作在活动开始前跑,不要等活动开始时再初始化,那时流量最大,初始化失败概率高。
- 兜底奖已配置且可用:测试兜底路径,确认池子空了还能正常发奖,不要等活动现场发现是空白页。
评论(0)
登录后参与评论。
还没有评论,来抢沙发吧。

