微信红包雨采用整数运算的二倍均值法实时分配:总金额转为“分”后全程用int计算,每轮区间为[1, (leftMoney / leftCount) × 2],最后一人直接取剩余金额;通过Collections.shuffle打乱顺序提升感知公平性,并用Redis Lua保证高并发下原子性。

微信红包雨系统中,随机金额分配不靠预存数组,而是靠实时计算+动态区间控制。核心不是 Collections 本身,而是用它辅助实现“二倍均值法”的逻辑闭环——比如打散顺序、封装结果、避免浮点误差带来的偏差。
关键:用整数运算替代浮点,单位统一为“分”
所有金额必须转为整型(单位:分),否则 random.uniform 或 double 运算会引入精度丢失,导致总额对不上。例如 10 元 = 1000 分,0.01 元 = 1 分。这是整个算法稳定运行的前提。
建议做法:
- 输入总金额先乘 100 并强制转 int,如 (int) Math.round(total * 100)
- 全程只用 int 运算,最后返回前再除以 100.0 转回元并保留两位小数
- 避免使用
ArrayList<double></double>存储中间结果,改用ArrayList<integer></integer>
二倍均值法的区间生成逻辑(每轮动态计算)
假设当前剩余金额为 leftMoney(分),剩余人数为 leftCount,则本轮可抢金额范围是:
[1, (leftMoney / leftCount) * 2](闭区间,单位:分)
注意:
– 除法用整数除法,Java 中 leftMoney / leftCount 自动截断小数,但不影响公平性(数学期望仍为均值)
– 上限不能为 0,所以要确保 leftMoney >= leftCount,否则提前校验失败
– 最后一人不参与随机,直接取 leftMoney
用 Collections.shuffle 打散领取顺序,消除时间偏见
原始二倍均值法存在“先抢易得大额”的倾向(第一轮上限最高)。为提升感知公平性,可在金额生成后、分发前做一次洗牌:
- 生成 N−1 个随机金额 + 1 个兜底金额,组成
ArrayList<integer> amounts</integer> - 调用 Collections.shuffle(amounts) 随机重排
- 再按新顺序逐一分配给抢到红包的用户(配合 Redis 队列或唯一请求 ID 控制)
这步不是必须,但能显著降低用户抱怨“手快反而吃亏”的概率。
实战中防并发与边界控制要点
高并发下,多个请求可能同时读到相同 leftMoney 和 leftCount,导致超发。必须结合原子操作:
- 用 Redis Lua 脚本封装“读剩余金额→计算→扣减→返回”全过程,保证原子性
- 每次分配前检查:
if (leftMoney - 金额列表最终需校验总和是否等于原始总金额(整数比对),不等则拒绝发放并告警
不复杂但容易忽略

















