抽獎模塊設計
思路
管理員在後臺設置獎品的中獎概率,未中獎概率 = 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 是否成立,然後才判斷是否需要更新,因此樂觀鎖的方案更好一些。
保證事務的原子性
以下操作應該作爲一個事務執行
- 用戶已抽獎次數的增加(或剩餘抽獎次數的減少)
- 獎品數據的減少(如果抽中獎品)
- 抽獎結果記錄的插入