Redis抢红包案例(二倍均值算法)
整体流程
- 发
- 抢
- 记(记录谁抢了多少、防止重复抢、如果红包到期没抢完,需要退回)
- 红包算法,保证每个红包大致有个范围,大家抢的差不多
需求举例
- 各种节假日,发红包+抢红包,100%对并发有要求,不能使用mysql
- 一个总的大红包,会有可能拆分成多个小红包,总金额=分金额1+分金额2....分金额N
- 每个人只能抢一次,显示剩余红包个数,需要记录抢的多少以及每个人的抢到时间、总耗时时间,防止作弊
- 红包过期,剩余退回
业务细节考虑
抢红包是不是并发场景?参与的人是不是很多?需不需要加锁?假如是redis,整体流程用什么数据结构来比较合适?
就拿并发场景来考虑,假如用redis,那么就不需要加锁,因为redis本来就是单线程,它不需要加锁就实现了原子性操作,实现了锁的效果。redis特性,高并发、实时、原子性、单线程。
首先,发红包
redis key用list结构,如redPackage:1,把红包的一些数额通过计算push进list
第二,抢红包
redis rpop,先到先得
难点
一、拆分算法
红包其实就是金额,拆分算法如何?给你100,分成10个小红包(金额有可能部分相同),如何随时拆分每个放置的额度。每个人至少抢一分钱
二、限制次数
每个人只能抢一次
三、原子性
每抢走一个红包就减小一个(类似库存)
二倍均值算法
剩余红包金额为M,剩余人数为N,那么有如下公式:
每次抢到的金额 = 随机区间 (0, M / N X 2),php就是 rand(0, M / N X 2),注意,最好把0替换成0.01,以免随机到0,这里我没做测试,是从其他网友帖子里看到的,本人没做测试,有机会测试一下
这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。
举个栗子:
假设有10个人,红包总额100元。100/10X2 = 20, 所以第一个人的随机范围是(0,20 ),平均可以抢到10元。
假设第一个人随机到10元,那么剩余金额是100-10 = 90 元。90/9X2 = 20, 所以第二个人的随机范围同样是(0,20 ),平均可以抢到10元。
假设第二个人随机到10元,那么剩余金额是90-10 = 80 元。80/8X2 = 20, 所以第三个人的随机范围同样是(0,20 ),平均可以抢到10元。
以此类推,每一次随机范围的均值是相等的。
//php代码 <?php //红包金额100元,分10次发送 //2位小数的随机数 function randomFloat($min = 0, $max = 1) { $num = $min + mt_rand() / mt_getrandmax() * ($max - $min); return sprintf("%.2f", $num); } //公用函数 function pingjunhongbao($total_money, $total_times) { $number = $total_times; for ($i = 1; $i <= $total_times; $i++) { if ($i == $total_times) { $red_bao = $total_money; } else { echo "total_money==" . $total_money; echo "<br>"; echo "number==" . $number; echo "<br>"; echo "total_money/total_times* 2==" . $pingjun = round($total_money / $number * 2, 2); echo "<br>"; echo '随机0.01和' . $pingjun . '之间两位小数的值是:' . randomFloat(0.01, $pingjun); echo "<br>"; $red_bao = round(randomFloat(0.01, $total_money / $number * 2), 2); } $total_money -= $red_bao; $number -= 1; echo "<br><br><br>"; print_r("第{$i}个人抢到{$red_bao}元红包,剩余红包{$total_money}元"); echo "<br>"; } } $total_money = 100; $total_times = 10; echo "共计" . $total_money . "元钱,分给" . $total_times . "个人"; echo "<hr>"; pingjunhongbao($total_money, $total_times); ?>
//结果 共计100元钱,分给10个人total_money==100 number==10 total_money/total_times* 2==20 随机0.01和20之间两位小数的值是:6.57 第1个人抢到0.59元红包,剩余红包99.41元 total_money==99.41 number==9 total_money/total_times* 2==22.09 随机0.01和22.09之间两位小数的值是:18.64 第2个人抢到13.18元红包,剩余红包86.23元 total_money==86.23 number==8 total_money/total_times* 2==21.56 随机0.01和21.56之间两位小数的值是:11.41 第3个人抢到7.26元红包,剩余红包78.97元 total_money==78.97 number==7 total_money/total_times* 2==22.56 随机0.01和22.56之间两位小数的值是:15.14 第4个人抢到21.95元红包,剩余红包57.02元 total_money==57.02 number==6 total_money/total_times* 2==19.01 随机0.01和19.01之间两位小数的值是:13.34 第5个人抢到0.7元红包,剩余红包56.32元 total_money==56.32 number==5 total_money/total_times* 2==22.53 随机0.01和22.53之间两位小数的值是:14.43 第6个人抢到2.3元红包,剩余红包54.02元 total_money==54.02 number==4 total_money/total_times* 2==27.01 随机0.01和27.01之间两位小数的值是:1.63 第7个人抢到4.14元红包,剩余红包49.88元 total_money==49.88 number==3 total_money/total_times* 2==33.25 随机0.01和33.25之间两位小数的值是:30.49 第8个人抢到27.25元红包,剩余红包22.63元 total_money==22.63 number==2 total_money/total_times* 2==22.63 随机0.01和22.63之间两位小数的值是:19.09 第9个人抢到7.97元红包,剩余红包14.66元 第10个人抢到14.66元红包,剩余红包0元
存储记录
至少有三个数据源保存
mysql、redis、mq。
为什么包含mq?因为在高并发场景下,临时存储数据的过度数据库就是mq,也不绝对使用。