抽奖系统的设计
思路
管理员在后台设置奖品的中奖概率,未中奖概率 = 1-中奖概率之和
。
伪随机数生成函数用于生成0-1之间的随机数,参考了官方手册中mt_getrandmax
示例。
http://php.net/manual/zh/function.mt-getrandmax.php
- 处理奖项数组,增加未中奖选项的概率
- 获得伪随机数
- 遍历奖项数据
- 通过奖项的获奖概率,设置坐标的左右区间
- 比较随机数是否落在区间
代码
<?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 是否成立,然后才判断是否需要更新,因此乐观锁的方案更好一些。
保证事务的原子性
以下操作应该作为一个事务执行
- 用户已抽奖次数的增加(或剩余抽奖次数的减少)
- 奖品数据的减少(如果抽中奖品)
- 抽奖结果记录的插入
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
- 上一篇: 解决crontab定时任务不能写入文件的问题
- 下一篇: 基于PHP的sso单点登录实例