当前位置:Gxlcms > PHP教程 > php结合redis秒杀商品的详解

php结合redis秒杀商品的详解

时间:2021-07-01 10:21:17 帮助过:16人阅读

这篇文章主要介绍了关于php结合redis 秒杀商品的详解,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

1 首先,一点点准备工作。

1.1建立商品表,订单表,并初始化数据

订单表。


1.2 将商品数据写入到redis 队列中去。

例如编号1 商品有100件。 就往 goods_1 队列里写100个1 进去。例用pop 操作的原子性(扛并发) 后面购买时,买一个就pop 一个。

  1. //代码使用yii 框架,重点在思路,其它框架做少量调整即可。
  2. $redis = self::createRedisObj(); //创建redis 对象,后面提供详细代码
  3. $sql = "select * from sec_goods";
  4. $rows = Yii::app()->db->createCommand($sql)->queryAll();
  5. foreach( $rows as $key => $row ):
  6. $goods_id = $row["id"];
  7. $stock_avail = $row["stock_avail"];
  8. $redis_key = "goods_".$goods_id;
  9. for($i =0 ; $i< $stock_avail; $i++){
  10. $redis->lpush($redis_key , 1);
  11. }
  12. echo $goods_id."llen is ".$redis->lLen($redis_key)."<br/>";
  13. endforeach;

建好后如下图。(真实情况下,后台可能出现库调整,需要对应的去同步redis 中的数据,实际项目中请留意,此处暂且不表)

2 无redis时,常规的购买代码。

  1. //用随机值模拟客户,商品,单次购买份数
  2. $uid = rand(1,10);
  3. $amount = rand(1,5);
  4. $goods_id = rand(1,6);
  5. $time = time();
  6. $this->buy($uid , $goods_id , $amount);
  7. public function buy($uid , $goods_id , $amount){
  8. //使用行锁.
  9. try {
  10. $trans = Yii::app()->db->beginTransaction();
  11. $sql = "select stock_avail from sec_goods where id = $goods_id for update"; //
  12. $stock_avail = Yii::app()->db->createCommand($sql)->queryScalar();
  13. if( $stock_avail >= $amount ){ //份额足够。
  14. $sn = date("YmdHis")."-".$uid."-".$goods_id.rand(1000,9999);
  15. $sql = "insert into sec_order set sn = '$sn',user_id = $uid, goods_id = $goods_id, create_at = $time,num = $amount";
  16. $bool = Yii::app()->db->createCommand($sql)->execute();
  17. if( !$bool ){ throw new Exception("执行失败".$sql); }
  18. $sql = "update sec_goods set stock_avail = stock_avail - $amount where id= $goods_id";
  19. $bool = Yii::app()->db->createCommand($sql)->execute();
  20. if( !$bool ){ throw new Exception("执行失败".$sql); }
  21. }
  22. $trans->commit();
  23. return true;
  24. } catch (Exception $e) {
  25. //日志记录
  26. $trans->rollback();
  27. return false;
  28. }
  29. }

然后使用 apache 的 ab 小工具进行测试。
-n 代表请求次数 -c 代表单次并发多少个请求。

ab -n 1000 -c 100 http://xxx

(把上述代码中的事务去掉,再ab 跑时会出现爆单,超卖问题 详情点击)

由于使用了行锁 for update。 基于事务的隔离性,一定是顺序的执行,所以上述代码,也不会出现超卖爆单问题。(10件库存卖出11件),但这样的代码,有个性能问题,就是有多少次并发请求,就会往数据库请求多少次。脆弱的mysql 很快就崩掉了。

3 结redis的秒杀代码。

终于上正菜了。。。。

  1. //code 3.1
  2. //用随机值模拟客户,商品,单次购买份数
  3. $uid = rand(1,10);
  4. $amount = rand(1,5);
  5. $goods_id = rand(1,6);
  6. $time = time();
  7. //用redis 校验,此次用户是否可以买。(库存是否充足)
  8. $redis = self::createRedisObj();
  9. $redis_key = "goods_".$goods_id;
  10. $len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。
  11. if( $len == 0 ){ exit("抢光了!"); }
  12. else if( $len < $amount){ exit("库存不足!"); }
  13. //验证通过,开始pop 出队列。 pop 一个,相当于买一个。
  14. for( $i =0 ; $i< $amount;$i++){
  15. $redis->rPop( $redis_key );
  16. }
  17. $bool = $this->buy($uid , $goods_id , $amount);
  18. if( !$bool ) { //如果购买失败,则把取出的redis 队列的数据,再压回去。(回充库存)
  19. for( $i =0 ; $i< $amount;$i++){
  20. $redis->lPush( $redis_key , 1);
  21. }
  22. }
  23. //创建redis 对象的。
  24. private static $_redis = null;
  25. /**
  26. * 创建一个redis 对象.
  27. * @return Redis
  28. */
  29. public static function createRedisObj(){ //2017-11-29 改为单例创建模式.
  30. if( ! self::$_redis){
  31. $redis = new Redis();
  32. $host = “192.168.1.xx”;
  33. $port = "6379";
  34. $redis->connect($host,$port);
  35. self::$_redis = $redis;
  36. }
  37. return self::$_redis;
  38. }

注意一个小细节,通常redis 会结合框架做缓存。 上述例子中,请注意在创redis 对象时,再单独指定一个库。(redis 一般有9个可选),避免服务器清缓存时将数据清空。

致此,上述代码完成了一个基础版本。
------------------------------------------------------------------------------------------------
然而在现象中,会随运营需求不断的产生变化。
随便举一例。
1 产品希望单用户3秒内仅能买一次。 无商品

2 产品希望单用户单个商品最多限买5 件。

碰到这种情况

问题1 处理如下

  1. //用随机值模拟客户,商品,单次购买份数
  2. $uid = rand(1,10);
  3. $amount = rand(1,5);
  4. $goods_id = rand(1,6);
  5. $time = time();
  6. $redis = self::createRedisObj();
  7. ////////////单用户限3秒内仅允许请求一次///////////////////////////////
  8. $lock_key = "uTimeLimit_".$uid;
  9. //按用户名编即可。 如果限用户针对指定商品,则lock_key 按uid+ goods_id 进行唯一编码
  10. if( $redis->get($lock_key)){
  11. $left_time = $redis->ttl($lock_key);
  12. exit($expire ."秒内只允许 $tag 一次!请".$left_time."之后再尝试");
  13. }else {
  14. $redis->setEx($lock_key , $expire , "1" );
  15. }
  16. //////////////////////////////////////////////////////////////////
  17. //用redis 校验,此次用户是否可以买。(库存是否充足)
  18. $len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。
  19. if( $len == 0 ){
  20. exit("抢光了!");
  21. }else if( $len < $amount){
  22. exit("库存不足!");
  23. }

// 后序购买流程。。。。。

问题2 修改如下。

  1. //用随机值模拟客户,商品,单次购买份数
  2. $uid = rand(1,10);
  3. $amount = rand(1,5);
  4. $goods_id = rand(1,6);
  5. $time = time();
  6. $redis = self::createRedisObj();
  7. ////////////单用户限5个处理///////////////////////////////
  8. //设一个hash 表, "user_buy" 然后 $uid . "_" . $goods_id 来记录购买的份数。
  9. $already_num = $redis->hGet("user_buy",$uid."_".$goods_id)? $redis->hGet("user_buy",$uid."_".$goods_id)
  10. :0; //求出已购买份额
  11. if( $already_num +$amount > 5){ exit("单用户单个商品限购买5个");}
  12. else{
  13. $new_num = $already_num +$amount ;
  14. $redis->hSet("user_buy",$uid."_".$goods_id , $new_num);
  15. }
  16. ////////////////////////////////////////////////////////////////////用redis 校验,此次用户是否可以买。(库存是否充足)
  17. $len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。
  18. if( $len == 0 ){ exit("抢光了!"); }
  19. else if( $len < $amount){ exit("库存不足!"); }// 后序购买流程。。。。。
  20. //如果购买失败
  21. $redis->hSet(
  22. "user_buy",$uid."_".$goods_id ,
  23. $redis->hGet("user_buy",$uid."_".$goods_id ) - $amount //失败时,则回复hash 表的数值
  24. );

这两个问题处理完毕,其它类似问题见招拆招。 有心的读者可以发现,这类修改有共同之处,可以适当封装下代码,更好的复用。

相关推荐:

PHP结合Redis来限制用户或者IP某个时间段内访问的次数

PHP结合Linux的cron命令实现定时任务实例

以上就是php结合redis 秒杀商品的详解的详细内容,更多请关注Gxl网其它相关文章!

人气教程排行