如果要实现一个抢红包的功能,红包金额是如何计算的?
- 标签:后端、场景题
🏛 业务架构师
抢红包金额计算的经典方案是二倍均值法,我会先讲算法,再讲它背后的业务设计感——这题算法本身只值一半分,另一半在资金安全和体验。
二倍均值法
每次随机区间为 [0.01, 2 × 剩余金额 / 剩余人数],保证每次期望相等:
java
// n 为剩余人数, money 为剩余金额(分)
if (n == 1) return money;
int max = 2 * money / n;
int cur = 1 + random.nextInt(max - 1);- 10 人 100 元:第一人最多抢 20 元,期望 10 元;无论谁先抢,期望一致
- 相比"完全随机"(先抢的可能抢走 99 元),方差更小、手气分布好看,用户的公平感是体验设计的一部分
工程实现:预分配 + 队列
红包发出时就按二倍均值把金额全部算好,写入 Redis 队列;抢的时候直接 LPOP——把"计算"从抢的高并发路径上摘掉,POP 是原子操作,天然防超抢。抢到后异步落库记账。
资金安全(架构师最看重的部分)
- 总额守恒:所有包金额之和必须严格等于红包总额,对账系统每天校验,这是资损红线。
- 过期退回:24 小时未抢完自动原路退回,余额不能悬空。
- 幂等:同一用户同一红包只能抢一次(Redis set 判重),网络重试不能多分钱。
- 审计:每个红包的拆分结果持久化,客诉时有据可查。
量级分档:中小业务 Redis 队列方案直接够用;微信级规模才需要按红包 ID 分片、多机房部署。绝大多数公司的问题不是算法扛不住,是对账没做严。
🏗 系统架构师
抢红包的核心是两件事:金额怎么拆(算法)和高并发下怎么保证不超发、总额守恒(架构)。
金额算法:二倍均值法 每次抽取区间 [0.01, 2 * 剩余均值]:假设 100 元 10 人,剩余均值 10 元,第一个人随机 0.01~20 元;抽完即时重算均值。这样期望均等、有随机波动、先抢后抢期望一致,是微信的实际方案。注意最后一人直接拿剩余全部,并用分为单位做整数运算,避免浮点误差。
并发架构(这才是我重点讲的)
- 预生成 + 存储:红包发出时就把 N 份金额用二倍均值拆好,作为一个 Redis List 一次写入(或 Lua 脚本原子拆分),抢的时候
LPOP——单次 O(1) 原子操作,天然防超发,比抢时再算金额简单一个量级。 - 原子扣减与入账拆开:抢到金额只写 Redis + 发一条 MQ 消息,账务落库、通知全部异步化。峰值 10 万人抢一个红包,DB 绝不能在同步链路上。
- 总额守恒对账:红包状态机(待抢/抢完/过期退款)+ 定时对账:
sum(领取记录) + 退款金额 = 红包总额,不一致告警。过期未领完自动退款走逆向流程。
一致性细节
- 幂等:同一用户同一红包只能抢一次,Redis
SETNX uid:rid挡重复请求。 - 挂在 Lua 里保证"判断剩余份数 + LPOP + 记录领取者"原子执行,避免两个用户抢到最后一份。
量级视角:微信春节峰值是每秒几十万次抢,靠的就是"同步链路只有 Redis 单命令、其余全异步"这条铁律。
💻 全栈开发专家
核心是二倍均值法:每次抢的人能拿的金额上限是剩余人均值的 2 倍,随机分布均匀且有人品爆发感。
算法(以分为单位,绝不用 double)
java
public static int randomAmount(int totalFen, int people) {
if (people == 1) return totalFen;
int max = totalFen / people * 2; // 剩余均值的两倍
int amt = ThreadLocalRandom.current().nextInt(1, max); // [1, max)
return Math.min(amt, totalFen - (people - 1)); // 保底每人至少 1 分
}比如 100 元 10 人:第一个人上限 20 元,抢完剩余重新算,最后一人拿全部剩余,期望每人 10 元。
工程落地(并发热点才是难点)
- 预生成:发红包时一次性算好 N 份金额,批量 insert 到
red_packet_item表,抢的时候只做UPDATE ... SET status=1, user_id=? WHERE id=? AND status=0,靠行锁 + 影响行数判断是否抢到。 - 高并发下我更倾向 Redis Lua 原子扣减库存 + 预生成金额用
LPOP弹出,异步入库。 - 幂等:同一用户对同一红包只能抢一次,用唯一索引
(red_id, user_id)兜底。
踩过的坑
- 金额一律
int/long存分,double 会有精度问题导致对不平账。 - 先抢后算(实时计算金额)在并发下要加锁,性能差,预拆分 + 抢占更新才是生产做法。
- 最后一份保底逻辑必须写,否则可能出现 0 分红包。