針對兩類發獎需求的四種抽獎邏輯及細節redis
1.必定中獎(獎品庫存不空的狀況下)
2.不必定中獎sql
1.獎品不超發
2.惟一獎品單次發放
3.對併發有必定的限制併發
1.根據獎品開放時間進行抽獎app
public function award($openid) { $award = Award::find()->where(['openid' => '']) ->andWhere(['>', 'open_at', 0])->andWhere(['<', 'open_at', time()]) ->orderBy('open_at ASC')->limit(1)->one(); if (!empty($award)) { $res = Award::updateAll( [ 'openid' => $openid ], 'code = :code AND openid = :openid', [ ':code' => $award['code'], ':openid' => '' ] ); if ($res) { return ArrayHelper::toArray($award); } } return []; }
這種方式,多用戶併發狀況下,會出現多個用戶相同獎品,因爲update語句限制,拿到相同獎品碼的用戶中只有一人能中得獎品。this
2.在開放時間的基礎上加上類型機率code
public function randAward($openid) { $number = rand(0, 100); $type = 5; if ($number < 10) { $type = 1; } else if ($number < 30) { $type = 2; } else if ($number < 70) { $type = 3; } else if ($number < 80) { $type = 4; } $award = Award::find()->where(['openid' => '']) ->andWhere(['>', 'open_at', 0])->andWhere(['<', 'open_at', time()]) ->andWhere(['type' => $type]) ->orderBy('open_at ASC')->limit(1)->one(); if (!empty($award)) { $res = Award::updateAll( [ 'openid' => $openid ], 'code = :code AND openid = :openid', [ ':code' => $award['code'], ':openid' => '' ] ); if ($res) { return ArrayHelper::toArray($award); } } return []; }
這種方式,也會出現多個用戶相同獎品,但加上type限制後,用戶被分散在各個類型中,未中獎機率會比上面的例子低。索引
3.利用Redis獎品池的概念進行發獎接口
public function redisAward($openid) { try { $redis = \Yii::$app->redis->client(); $code = $redis->LPop(self::AWARD_LIST_KEY); } catch (Exception $err) { return []; } $res = Award::updateAll( [ 'openid' => $openid ], 'code = :code AND openid = :openid', [ ':code' => $code, ':openid' => '' ] ); if ($res) { $award = Award::find()->where(['code' => $code])->limit(1)->one(); return ArrayHelper::toArray($award); } return []; }
這種利用預先生成獎品池的方式,獎品池不空的狀況下,每一個用戶都會取走不一樣獎品碼,要注意的是 前期生成獎品池及後期操做獎品池時,防止獎品碼複用it
4.根據獎品開放時間(類型)進行抽獎,換成用sql語句進行發獎io
public function sqlAward($openid) { $sql = "UPDATE award SET openid = :openid WHERE open_at > 0 AND openid = '' AND open_at < :time ORDER BY open_at ASC LIMIT 1"; $res = \Yii::$app->db->createCommand($sql, [':time' => time(), ':openid' => $openid])->execute(); if ($res) { return Award::find()->where(['openid' => $openid])->limit(1)->asArray()->one(); } return []; }
必定中獎需求下,建議採用Redis獎品池或者sql語句進行update
以上四種方式在多用戶併發的狀況下帶來不同的結果
除了多用戶併發,還會出現惡刷狀況,就是同一用戶併發請求
這種狀況應該在真正進入抽獎邏輯以前進行限制
能夠根據實際需求搭配如下方式進行限制
public function actionAward() { $openid = 'okjkLj7-UL4gXknY9bDkFn0O6Jos'; $redis = \Yii::$app->redis->client(); // 用戶單次數 if (!$redis->sAdd(self::USER_LIST_KEY, $openid)) { return []; } return $this->sqlAward($openid); }
也能夠限制抽獎人數
public function actionAward() { $openid = CommonTool::randString(32); try { $redis = \Yii::$app->redis->client(); // 抽獎用戶數量 $list = $redis->sMembers(self::USER_LIST_KEY); if (count($list) > 1000) { return ; } } catch (Exception $err) { } $award = $this->sqlAward($openid); }
H5活動抽獎接口須要注意幾點1.檢查用戶有效性2.限制單用戶訪問次數3.使用機率讓用戶分流,從而控制真正進入抽獎邏輯的請求4.記錄抽獎領獎等相關操做的時間設備IP等..5.控制獎品的分佈(時間,插空,機率等)6.作好索引關係