Yihui’s Blog

抢红包时,红包金额如何计算?

日期:2026-07-11
标签:#面试 #八股 #后端 #算法 #场景题

一句话答案

常用二倍均值法动态生成金额,但生产系统必须以“分”为单位计算,并同时保证总额守恒、每份不低于最小金额、并发原子性和重复领取幂等。

面试口语版

假设剩余金额为 M 分、剩余人数为 N,每次随机范围可以取 [1, 2M/N],领取后原子更新剩余金额和人数,最后一个人拿走全部余额,这就是二倍均值法。它简单且每个人期望值接近,但不保证绝对均匀。生产中金额不能用浮点数,要用整数分或 Decimal;领取操作可以用 Redis Lua 或数据库条件更新保证原子性,并用红包 ID 加用户 ID 做唯一约束,防止重复领取。

原理拆解

max = floor(2 * remainingAmount / remainingCount)
amount = random(minAmount, max)
最后一份 = remainingAmount
  • 如果业务设置单份最大/最小金额,要进一步收紧范围,确保剩余金额仍能满足剩余人数。
  • 更强公平性可以预先切分金额、校验总额后洗牌;这样领取路径更短,但发红包时计算量更大。
  • 状态包括未开始、领取中、已领完、已过期;过期余额按资金规则退款。

关键细节

  • 钱包扣款、红包资金冻结和领取入账需要可对账的资金流水。
  • Redis 只作为高并发领取入口时,最终账务仍应落可靠存储,并通过 MQ 和补偿任务最终一致。
  • 随机数不应影响资金守恒;任何异常都必须能由流水恢复。

面试官追问

  1. 二倍均值法公平吗?
  2. Redis 扣减成功但入账失败怎么办?
  3. 如何防止同一用户重复抢?
  4. 红包过期退款如何保证幂等?

面试官追问参考答案

1. 二倍均值法公平吗?

它保证每次随机上限与剩余平均值相关,长期看期望接近,但各位置的分布并不完全一致,也不能保证方差或每人体验相同。如果业务要求可审计的严格分布,可预先按约束生成全部金额、校验总额后安全洗牌。

2. Redis 扣减成功但入账失败怎么办?

Redis 领取结果先形成唯一领取流水并可靠发送到账务系统,消费失败持续重试;账务入账以流水号幂等。长期失败进入补偿和人工队列,红包状态不能直接视为结算完成;若最终回滚领取,必须按同一流水原子恢复余额和资格。

3. 如何防止同一用户重复抢?

Lua 脚本在一个原子操作中检查用户领取标记、剩余份数并写入领取结果。持久层对 redPacketId + userId 建唯一索引兜底;客户端 requestId 只解决同一请求重放,不能替代业务唯一约束。

4. 红包过期退款如何保证幂等?

为红包生成唯一退款业务号,状态通过条件更新从可领取变为退款中,只有抢到状态迁移的任务发起退款。支付/钱包侧按退款号幂等,回调和主动查询重复执行均更新同一记录,对账任务处理未知状态。

学习清单

  • 能手算一次二倍均值法。
  • 能说明原子领取、资金流水与补偿流程。
Maintained by · YihuiEdit on GitHub

Keep reading

View all posts