Redis抢红包案例(二倍均值算法)

整体流程

  1. 记(记录谁抢了多少、防止重复抢、如果红包到期没抢完,需要退回)
  2. 红包算法,保证每个红包大致有个范围,大家抢的差不多

需求举例

  1. 各种节假日,发红包+抢红包,100%对并发有要求,不能使用mysql
  2. 一个总的大红包,会有可能拆分成多个小红包,总金额=分金额1+分金额2....分金额N
  3. 每个人只能抢一次,显示剩余红包个数,需要记录抢的多少以及每个人的抢到时间、总耗时时间,防止作弊
  4. 红包过期,剩余退回

业务细节考虑

抢红包是不是并发场景?参与的人是不是很多?需不需要加锁?假如是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,也不绝对使用。

 

posted @ 2018-09-28 16:06  温柔的风  阅读(163)  评论(0编辑  收藏  举报