入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

抽奖系统的设计

创建时间:2017-05-27 投稿人: 浏览次数:660

思路

管理员在后台设置奖品的中奖概率,未中奖概率 = 1-中奖概率之和

伪随机数生成函数用于生成0-1之间的随机数,参考了官方手册中mt_getrandmax示例。
http://php.net/manual/zh/function.mt-getrandmax.php

  1. 处理奖项数组,增加未中奖选项的概率
  2. 获得伪随机数
  3. 遍历奖项数据
  4. 通过奖项的获奖概率,设置坐标的左右区间
  5. 比较随机数是否落在区间

代码

<?php
function randomFloat($min = 0, $max = 1) {
    return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
$lottery_arr = [
    ["name"=>"非洲5日游","chance"=>0.1],
    ["name"=>"朝鲜3日游","chance"=>0.2],
    ["name"=>"云南3日游","chance"=>0.3],
    ["name"=>"京东购物卡","chance"=>0.25]
];

$lottery_arr[] = ["name"=>"谢谢参与","chance"=>(1-array_sum($lottery_arr))];

$res_arr = [
    "非洲5日游"=>0,
    "朝鲜3日游"=>0,
    "云南3日游"=>0,
    "京东购物卡"=>0,
    "谢谢参与"=>0
];

for($i=0;$i<10000;$i++){
    $randomFloat = randomFloat();
    $baseFloat = 0;
    foreach($lottery_arr as $item){
        if( $randomFloat > $baseFloat && $randomFloat <= ($baseFloat+$item["chance"]) ){
            $res_arr[$item["name"]]++;
        }
        $baseFloat += $item["chance"];
    }
}

var_dump($res_arr);

限制请求次数

在执行抽奖流程之前,应先判断用户上次抽奖的时间以及总共的抽奖次数,对于不符合要求的无需进入抽奖流程。

避免超发问题

用户抽中奖之后,一般会检查奖品剩余量,然后再 修改奖品的数量。

SELECT used_num,total_num from awards WHERE id = 1;

如果 total_num> used_num,则

UPDATE awards SET used_num = used_num+1 WHERE id = 1;

假设此时total_num = 100,used_num = 99。

由于读操作没有加锁,进程1读取used_num和total_num是符合要求的。

但是在更新used_num之前时,进程2也读取了used_num和total_num的值发现自己也是符合要求的。

由于进程1在更新used_num字段,锁了该行,进程2则在进程1更新之后,再次更新了used_num字段,此时used_num=101,超发了一个。

解决该问题有两种方案,分别是悲观锁和乐观锁,其中乐观锁的并发性能够更好一些。

悲观锁

修改SELECT语句(悲观锁在事务中生效,事务commit或rollback之后,释放锁)

SELECT used_num,total_num from awards WHERE id = 1 FOR UPDATE

乐观锁

修改UPDATE语句

UPDATE awards SET used_num = used_num+1 WHERE id = 1 AND used_num<total_num;

由于在SELECT语句之后,还需要判断 used_num < total_num 是否成立,然后才判断是否需要更新,因此乐观锁的方案更好一些。

保证事务的原子性

以下操作应该作为一个事务执行

  1. 用户已抽奖次数的增加(或剩余抽奖次数的减少)
  2. 奖品数据的减少(如果抽中奖品)
  3. 抽奖结果记录的插入
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。