news 2026/4/16 12:33:30

高并发场景下的“抢红包”算法设计:如何保证红包金额随机且不超发?(二倍均值法)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
高并发场景下的“抢红包”算法设计:如何保证红包金额随机且不超发?(二倍均值法)

🧧 前言:为什么“抢红包”这么难?

你以为抢红包只是Random()一下吗?
如果是 100 块钱发给 10 个人:

  • 第一个人Random(0, 100),随到了 99 元。
  • 剩下 9 个人分 1 块钱?这游戏还能玩?

核心难点在于:

  1. 随机性:每个人抢到的金额要随机,但要符合正态分布(大部分人手气差不多,少数人运气爆棚)。
  2. 公平性:越早抢和越晚抢,获取金额的“数学期望”必须相等。
  3. 高并发:春节除夕夜,几亿人同时点,数据库直接炸裂。
  4. 不超发:绝对不能出现 100 块钱分出了 101 块的情况(资损)。

今天,我们重点讲业界最通用的算法——二倍均值法,以及如何用Redis Lua脚本落地。


🧮 核心算法:二倍均值法 (Double Mean Method)

如果让你设计,你可能想过“预先生成好 10 个随机数放到数组里”。这没问题,但如果红包金额很大、人数很多,存储成本较高。

二倍均值法是一种实时计算算法。

1. 算法公式

假设剩余金额为M,剩余人数为N
每次抢到的金额X=Random(0.01, (M / N) * 2)

2. 原理推导

假设 100 元发给 10 人。

  • 第 1 人

  • 均值 = 100 / 10 = 10 元。

  • 范围 = [0.01, 20]。

  • 假设抢到5 元

  • 第 2 人

  • 剩余金额 95 元,剩余 9 人。

  • 均值 = 95 / 9 ≈ 10.55 元。

  • 范围 = [0.01, 21.1]。

  • 假设抢到15 元

  • 最后 1 人:直接拿走剩余所有金额。

数学证明:
每次抢夺的期望值始终等于剩余人均金额。这保证了无论你第几个来,理论上抢到的钱平均值是一样的。


💻 代码实现 (Java 版)

算法逻辑本身很简单,注意处理最小单位(1分钱)

publicclassRedPacketUtil{/** * 二倍均值法计算红包 * @param restMoney 剩余金额 (单位:分) * @param restPeople 剩余人数 * @return 本次抢到的金额 */publicstaticintsplitRedPacket(intrestMoney,intrestPeople){// 1. 如果是最后一人,拿走所有if(restPeople==1){returnrestMoney;}// 2. 二倍均值法的核心公式// 范围:[1, (剩余金额/剩余人数) * 2)// 注意:Random 是左闭右开,所以要 -1 留给剩下的人至少 1 分钱intmax=(restMoney/restPeople)*2;// 随机生成金额,至少 1 分钱intamount=(int)(Math.random()*max);if(amount<=0)amount=1;// 3. 兜底逻辑:不能让剩下的人没钱分// 如果本次抢太多,导致剩下的人分不到 1 分钱,要强行截断if(restMoney-amount<restPeople-1){amount=restMoney-(restPeople-1);}returnamount;}}

🏗️ 高并发架构:Redis + Lua 脚本

算法有了,怎么抗住 10万 QPS?
绝对不能用 MySQL 的行锁(悲观锁),必死无疑。
必须使用Redis进行内存计算,并配合Lua 脚本保证原子性(Atomic)。

1. 架构流程图
1. 执行 Lua 脚本
2. 库存/金额不足
3. 扣减成功, 返回金额
4. 异步写入 (削峰)
5. 消费入库
用户请求: 抢红包
Nginx 负载均衡
业务服务 Cluster
Redis (单线程原子性)
返回: 抢光了
抢到红包
消息队列 (RocketMQ/Kafka)
MySQL (最终一致性)
2. 核心 Lua 脚本

Redis 的 Lua 脚本是原子执行的,中间不会被插入其他命令。我们把“查询剩余金额”、“计算二倍均值”、“扣减库存”这三步合为一步。

-- keys[1]: 红包信息的 Key (Hash结构)-- argv[1]: 用户 IDlocalkey=KEYS[1]localuserId=ARGV[1]-- 1. 校验是否抢过 (幂等性)ifredis.call('hexists',key..':records',userId)==1thenreturn-1-- 已经抢过了end-- 2. 获取剩余金额和人数localrestMoney=tonumber(redis.call('hget',key,'money'))localrestPeople=tonumber(redis.call('hget',key,'count'))-- 3. 校验库存ifrestPeople<=0thenreturn0-- 抢光了end-- 4. 执行二倍均值法算法 (简化版)localamount=0ifrestPeople==1thenamount=restMoneyelse-- Lua 的随机数localmax=math.floor((restMoney/restPeople)*2)amount=math.random(1,max)end-- 5. 扣减 Redis 库存redis.call('hincrby',key,'money',-amount)redis.call('hincrby',key,'count',-1)-- 记录领取记录redis.call('hset',key..':records',userId,amount)returnamount

📉 方案优缺点分析

方案优点缺点适用场景
预分配法(发红包时生成所有金额存 List)抢红包速度极快 (LPOP),逻辑简单占用内存大,发红包时耗时标准红包,人数确定
二倍均值法(实时计算)内存占用极小,不需要预存列表每次需要计算,Redis Lua 逻辑稍复杂超大额/超多人红包

📝 总结

设计一个抢红包系统,不仅仅是写个Random

  1. 算法层:用二倍均值法保证数学期望的公平。
  2. 存储层:用Redis Atomicity解决超卖。
  3. 持久层:用MQ 异步解耦保护 MySQL。

这一套组合拳打下来,面试官基本就被你折服了。


博主留言:
想获取“Redis Lua 脚本完整版”“SpringBoot 抢红包项目源码”吗?
在评论区回复“红包”,我打包发给你!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:57:23

测试深度思考:从执行者到战略家的思维跃迁

在软件测试领域&#xff0c;我们常陷于日常的用例执行、缺陷跟踪和报告编写中&#xff0c;仿佛测试就是一场“找茬游戏”。然而&#xff0c;随着敏捷开发、DevOps和AI技术的普及&#xff0c;测试角色正从被动的质量检验者向主动的质量赋能者演变。深度思考&#xff0c;正是这一…

作者头像 李华
网站建设 2026/4/14 14:20:00

10 个AI论文工具,MBA学生高效写作必备!

10 个AI论文工具&#xff0c;MBA学生高效写作必备&#xff01; AI 工具助力论文写作&#xff0c;MBA 学生的高效之选 在当今快节奏的 MBA 学习中&#xff0c;论文写作已成为一项重要任务。无论是案例分析、商业计划书还是研究论文&#xff0c;都需要学生具备高效的写作能力与…

作者头像 李华
网站建设 2026/4/16 11:05:36

Langchain-Chatchat与Istio服务网格集成:精细化流量治理

Langchain-Chatchat与Istio服务网格集成&#xff1a;精细化流量治理 在企业加速构建AI原生能力的今天&#xff0c;如何在保障数据主权的前提下&#xff0c;将大语言模型&#xff08;LLM&#xff09;系统稳定、可控地接入生产环境&#xff0c;成为技术团队面临的核心挑战。尤其在…

作者头像 李华
网站建设 2026/3/31 19:28:53

Langchain-Chatchat支持哪些文件格式?一文讲清输入源配置

Langchain-Chatchat 支持哪些文件格式&#xff1f;一文讲清输入源配置 在企业知识管理日益复杂的今天&#xff0c;如何让堆积如山的PDF、Word文档和内部手册“开口说话”&#xff0c;成了智能化落地的关键一步。很多团队尝试引入大模型来构建智能问答系统&#xff0c;却发现通用…

作者头像 李华
网站建设 2026/4/16 12:28:17

10 个AI论文工具,助你轻松搞定研究生论文!

10 个AI论文工具&#xff0c;助你轻松搞定研究生论文&#xff01; AI 工具助力学术写作&#xff0c;轻松应对论文挑战 在研究生阶段&#xff0c;论文写作不仅是学术能力的体现&#xff0c;也是对时间与精力的巨大考验。随着人工智能技术的发展&#xff0c;越来越多的 AI 工具被…

作者头像 李华