0

0

php结合redis 秒杀商品的详解

不言

不言

发布时间:2018-05-09 11:52:34

|

5172人浏览过

|

来源于php中文网

原创

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

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

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

 订单表。

立即学习PHP免费学习笔记(深入)”;


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

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

//代码使用yii 框架,重点在思路,其它框架做少量调整即可。 
 $redis = self::createRedisObj(); //创建redis 对象,后面提供详细代码

        $sql = "select * from sec_goods";
        $rows = Yii::app()->db->createCommand($sql)->queryAll();

        foreach( $rows as $key => $row ):
            $goods_id = $row["id"];
            $stock_avail =  $row["stock_avail"];
            $redis_key = "goods_".$goods_id;
            for($i =0 ; $i< $stock_avail; $i++){
                $redis->lpush($redis_key , 1);
            }
            echo $goods_id."llen is ".$redis->lLen($redis_key)."
"; endforeach;

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

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

//用随机值模拟客户,商品,单次购买份数 
 $uid = rand(1,10);
        $amount = rand(1,5);
        $goods_id = rand(1,6);
        $time = time();
$this->buy($uid , $goods_id , $amount);

public function buy($uid , $goods_id , $amount){

     //使用行锁.
        try {
            $trans = Yii::app()->db->beginTransaction();

            $sql = "select stock_avail from sec_goods where id = $goods_id for update";  //
            $stock_avail = Yii::app()->db->createCommand($sql)->queryScalar();

            if( $stock_avail >= $amount ){  //份额足够。
                $sn = date("YmdHis")."-".$uid."-".$goods_id.rand(1000,9999);

                $sql = "insert into sec_order set sn = '$sn',user_id = $uid, goods_id = $goods_id, create_at = $time,num = $amount";
                $bool = Yii::app()->db->createCommand($sql)->execute();
                if( !$bool ){ throw new Exception("执行失败".$sql); }

                $sql = "update sec_goods set stock_avail = stock_avail - $amount  where id= $goods_id";
                $bool = Yii::app()->db->createCommand($sql)->execute();
                if( !$bool ){ throw new Exception("执行失败".$sql); }
            }
            $trans->commit();
            return true;
        } catch (Exception $e) {
            //日志记录
            $trans->rollback();
            return false;
        }
}

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

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

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

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

冰兔(Btoo)网店系统
冰兔(Btoo)网店系统

系统简介:冰兔BToo网店系统采用高端技术架构,具备超强负载能力,极速数据处理能力、高效灵活、安全稳定;模板设计制作简单、灵活、多元;系统功能十分全面,商品、会员、订单管理功能异常丰富。秒杀、团购、优惠、现金、卡券、打折等促销模式十分全面;更为人性化的商品订单管理,融合了多种控制和独特地管理机制;两大模块无限级别的会员管理系统结合积分机制、实现有效的推广获得更多的盈利!本次更新说明:1. 增加了新

下载

3 结redis的秒杀代码。

终于上正菜了。。。。

//code 3.1   
//用随机值模拟客户,商品,单次购买份数 
       $uid = rand(1,10);
$amount = rand(1,5); 
$goods_id = rand(1,6);
 $time = time(); 

//用redis 校验,此次用户是否可以买。(库存是否充足)
 $redis = self::createRedisObj(); 
$redis_key = "goods_".$goods_id; 
$len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。 
if( $len == 0 ){ exit("抢光了!"); }
else if( $len < $amount){ exit("库存不足!"); }

//验证通过,开始pop 出队列。  pop 一个,相当于买一个。  
        for( $i =0 ; $i<  $amount;$i++){
             $redis->rPop( $redis_key );
        }

        $bool = $this->buy($uid , $goods_id , $amount);
        if( !$bool ) {   //如果购买失败,则把取出的redis 队列的数据,再压回去。(回充库存)
         for( $i =0 ; $i<  $amount;$i++){
             $redis->lPush( $redis_key , 1);
         }
     }
//创建redis 对象的。
  private static $_redis = null;
    /**
     * 创建一个redis 对象.
     * @return Redis
     */
    public static function createRedisObj(){ //2017-11-29 改为单例创建模式.

        if( ! self::$_redis){
            $redis = new Redis();
            $host = “192.168.1.xx”;
            $port = "6379";
            $redis->connect($host,$port);
            self::$_redis = $redis;
        }
        return self::$_redis;
    }

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

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

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

碰到这种情况

问题1 处理如下

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

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

问题2 修改如下。

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

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

相关推荐:

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

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



相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
C++ 高级模板编程与元编程
C++ 高级模板编程与元编程

本专题深入讲解 C++ 中的高级模板编程与元编程技术,涵盖模板特化、SFINAE、模板递归、类型萃取、编译时常量与计算、C++17 的折叠表达式与变长模板参数等。通过多个实际示例,帮助开发者掌握 如何利用 C++ 模板机制编写高效、可扩展的通用代码,并提升代码的灵活性与性能。

10

2026.01.23

php远程文件教程合集
php远程文件教程合集

本专题整合了php远程文件相关教程,阅读专题下面的文章了解更多详细内容。

29

2026.01.22

PHP后端开发相关内容汇总
PHP后端开发相关内容汇总

本专题整合了PHP后端开发相关内容,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

php会话教程合集
php会话教程合集

本专题整合了php会话教程相关合集,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

宝塔PHP8.4相关教程汇总
宝塔PHP8.4相关教程汇总

本专题整合了宝塔PHP8.4相关教程,阅读专题下面的文章了解更多详细内容。

13

2026.01.22

PHP特殊符号教程合集
PHP特殊符号教程合集

本专题整合了PHP特殊符号相关处理方法,阅读专题下面的文章了解更多详细内容。

11

2026.01.22

PHP探针相关教程合集
PHP探针相关教程合集

本专题整合了PHP探针相关教程,阅读专题下面的文章了解更多详细内容。

8

2026.01.22

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

55

2026.01.22

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.3万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 6.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号