关于建设网站的培训知识,广州软件制作公司,如何做到精准客户推广,安卓app怎么开发#x1f9e7; 前言#xff1a;为什么“抢红包”这么难#xff1f;
你以为抢红包只是 Random() 一下吗#xff1f;
如果是 100 块钱发给 10 个人#xff1a;
第一个人 Random(0, 100)#xff0c;随到了 99 元。剩下 9 个人分 1 块钱#xff1f;这游戏还能玩#xff1f;
… 前言为什么“抢红包”这么难你以为抢红包只是Random()一下吗如果是 100 块钱发给 10 个人第一个人Random(0, 100)随到了 99 元。剩下 9 个人分 1 块钱这游戏还能玩核心难点在于随机性每个人抢到的金额要随机但要符合正态分布大部分人手气差不多少数人运气爆棚。公平性越早抢和越晚抢获取金额的“数学期望”必须相等。高并发春节除夕夜几亿人同时点数据库直接炸裂。不超发绝对不能出现 100 块钱分出了 101 块的情况资损。今天我们重点讲业界最通用的算法——二倍均值法以及如何用Redis Lua脚本落地。 核心算法二倍均值法 (Double Mean Method)如果让你设计你可能想过“预先生成好 10 个随机数放到数组里”。这没问题但如果红包金额很大、人数很多存储成本较高。二倍均值法是一种实时计算算法。1. 算法公式假设剩余金额为M剩余人数为N。每次抢到的金额XRandom(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(restPeople1){returnrestMoney;}// 2. 二倍均值法的核心公式// 范围[1, (剩余金额/剩余人数) * 2)// 注意Random 是左闭右开所以要 -1 留给剩下的人至少 1 分钱intmax(restMoney/restPeople)*2;// 随机生成金额至少 1 分钱intamount(int)(Math.random()*max);if(amount0)amount1;// 3. 兜底逻辑不能让剩下的人没钱分// 如果本次抢太多导致剩下的人分不到 1 分钱要强行截断if(restMoney-amountrestPeople-1){amountrestMoney-(restPeople-1);}returnamount;}}️ 高并发架构Redis Lua 脚本算法有了怎么抗住 10万 QPS绝对不能用 MySQL 的行锁悲观锁必死无疑。必须使用Redis进行内存计算并配合Lua 脚本保证原子性Atomic。1. 架构流程图1. 执行 Lua 脚本2. 库存/金额不足3. 扣减成功, 返回金额4. 异步写入 (削峰)5. 消费入库用户请求: 抢红包Nginx 负载均衡业务服务 ClusterRedis (单线程原子性)返回: 抢光了抢到红包消息队列 (RocketMQ/Kafka)MySQL (最终一致性)2. 核心 Lua 脚本Redis 的 Lua 脚本是原子执行的中间不会被插入其他命令。我们把“查询剩余金额”、“计算二倍均值”、“扣减库存”这三步合为一步。-- keys[1]: 红包信息的 Key (Hash结构)-- argv[1]: 用户 IDlocalkeyKEYS[1]localuserIdARGV[1]-- 1. 校验是否抢过 (幂等性)ifredis.call(hexists,key..:records,userId)1thenreturn-1-- 已经抢过了end-- 2. 获取剩余金额和人数localrestMoneytonumber(redis.call(hget,key,money))localrestPeopletonumber(redis.call(hget,key,count))-- 3. 校验库存ifrestPeople0thenreturn0-- 抢光了end-- 4. 执行二倍均值法算法 (简化版)localamount0ifrestPeople1thenamountrestMoneyelse-- Lua 的随机数localmaxmath.floor((restMoney/restPeople)*2)amountmath.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。算法层用二倍均值法保证数学期望的公平。存储层用Redis Atomicity解决超卖。持久层用MQ 异步解耦保护 MySQL。这一套组合拳打下来面试官基本就被你折服了。博主留言想获取“Redis Lua 脚本完整版”和“SpringBoot 抢红包项目源码”吗在评论区回复“红包”我打包发给你