整体流程
- 发
- 抢
- 记(记录谁抢了多少、防止重复抢、如果红包到期没抢完,需要退回)
- 红包算法,保证每个红包大致有个范围,大家抢的差不多
需求举例
业务细节考虑
抢红包是不是并发场景?参与的人是不是很多?需不需要加锁?假如是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,也不绝对使用。