针对两类发奖需求的四种抽奖逻辑及细节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.作好索引关系