美文网首页开源项目
NoSQL(Redis)秒杀

NoSQL(Redis)秒杀

作者: 空留灯半盏 | 来源:发表于2018-07-06 16:06 被阅读35次

    NoSQL(Redis)秒杀

    概念

    秒杀
    说明:秒杀就是指商家的限时大甩卖(商家为了售卖商品所采取的一种销售手段)
    特征:1-限时,2-低价
    种类:一元秒杀、低价限量秒杀、低价限时限量秒杀
    好处:因为秒杀产品参与者数量众多,可以瞬间聚集人气,提升品牌影响力,是一种不错的促销手段。
    
    并发
    生活中:指同时有n个用户一起去收营台结账的表现可以称之为并发
    网络中:指同时有n个用户一起访问网站的表现可以称之为并发
    并发导致的问题:
    生活中-忙不过来,程序中-服务器可能崩溃或者出现意外结果(负库存)
        在计算机中:通过消息队列实现
    

    MySQL负库存(秒杀可能出现的问题)

    修改mysql.ini max_connections = 10 #声明同时支持多少个用户连接

    打开 navicat.exe 执行sql语句

    create database if not exists miaosha charset=utf8;
    use miaosha;
    create table goods (id int primary key auto_increment,num int) engine=innodb;
    insert into goods values (null, 100);
    

    在站点目录下创建mysql.php输入下述命令

    <?php
    #语法:ab -n 1000 -c 100 请求地址
    #说明:n-请求总数, c- 每次请求量
    
    //1.创建PDO对象
    $pdo = new PDO('mysql:dbname=miaosha', 'root', 'root');
    
    //2.查询库存
    $pdoStatement = $pdo->query('select num from goods where id = 1');
    $res = $pdoStatement->fetch(PDO::FETCH_ASSOC);
    $num = $res['num'];
    
    //3.判断库存
    if ($num) {
        //减库存
        $pdo->exec('update goods set num=num-1 where id = 1');
        echo '抢购成功';
    } else {
        echo '对不起,你来晚了,库存不足';
    }
    

    通过本地Apache安装目录下bin目录中的ab测压工具测试并发

    ab.exe -n 1000 -c 100 http://127.0.0.1/mysql.php
    

    多测试几次 查看数据库 可能会出现负库存 这些是并发量高,数据处理不过来,当前面用户下单时,后面用户也读取到了库存数据 就会出现负库存

    Redis消息队列(解决秒杀问题)

    使用Workerman框架

    下载Workerman框架....

    在站点目录下创建testworkerman.php输入手册中的定时器代码

    <?php  
    use \Workerman\Worker;
    use \Workerman\Lib\Timer;
    require_once __DIR__ . '/Workerman/Autoloader.php';
    
    $task = new Worker();
    // 开启多少个进程运行定时任务,注意业务是否在多进程有并发问题
    $task->count = 1;
    $task->onWorkerStart = function($task)
    {
        // 每2.5秒执行一次
        $time_interval = 2.5;
        Timer::add($time_interval, function()
        {
            echo "task run\n";
        });
    };
    
    // 运行worker
    Worker::runAll();
    

    打开DOS窗口通过php.exe执行testworkerman.php文件查看效果

    实现

    登录redis设置存放商品秒杀数据信息

        flushall
        hmset goods_seckill_1 start_time 0 stop_time 0 price 30 real_num 3 seckill_num 3
        hmset goods_seckill_2 start_time 0 stop_time 0 price 30 real_num 2 seckill_num 2
    

    在站点目录下创建redis.php输入下述命令

    <?php
    #步骤1:接受数据
    $uid = 1;
    $goods_id = 1;
    
    #步骤2:连接Redis
    $redis = new Redis;
    $redis->connect('192.168.159.128', 6379);
    $redis->auth('123');
    $redis->select(0);
    
    #步骤3:过滤(判断时间和库存)
    //获取商品信息
    $goodsInfo = $redis->hmget("goods_seckill_{$goods_id}"., array(
        'start_time', 'stop_time', 'real_num', 'seckill_num', 'price'
    ));
    //判断是否开始
    //判断是否结束
    //判断库存
    if ($goodsInfo['seckill_num'] < 1) {
        echo json_encode(array('state' => 0, '对不起,宝贝已被抢完!'));
        die;
    }
    
    #步骤4:将用户请求加入消息队列中
    $len = $redis->lpush("goods_seckill_{$goods_id}_rs", $uid.'%'.$goods_id.'%'.$goodsInfo['price']);
    
    #步骤5:判断库存(规则:队列中前n个抢购成功)
    if ($len > $goodsInfo['real_num']) {
    //抢购失败(队列长度 > 库存)
        echo json_encode(array('state' => 0, '对不起,宝贝已被抢完!'));
        die;
    } else {
    //抢购成功,减库存(注:千万不能直接操作mysql因为有并发限制)
        echo json_encode(array('state' => 1, '秒杀成功'));
        die;
    }
    

    在站点目录创建workerman.php输入下述命令

    <?php
    use \Workerman\Worker;
    use \Workerman\Lib\Timer;
    require_once __DIR__ . '/Workerman/Autoloader.php';
    
    $task = new Worker();
    // 开启多少个进程运行定时任务,注意业务是否在多进程有并发问题
    $task->count = 1;
    $task->onWorkerStart = function($task)
    {
        //每0.1秒执行一次(精度可以达到毫秒0.001)
        $time_interval = 0.1; 
        Timer::add($time_interval, function()
        {
            $goods_id = 1;
            #步骤1:连接Redis
            $redis = new Redis;
            $redis->connect('192.168.159.128', 6379);
            $redis->auth('123');
            $redis->select(0);
            #步骤2:获取秒杀相关信息
            $allowBuyNum = $redis->hget("goods_seckill_{$goods_id}", 'seckill_num'); //秒杀剩余库存
            $orderInfoString = $redis->rpop("goods_seckill_{$goods_id}_rs");                     //队列抢购用户信息
            #步骤3:判断(有库存 && 有人抢购)
            if($allowBuyNum > 0 && $orderInfoString) 
            {
                echo "allowBuyNum:$allowBuyNum\n";
    
                #步骤4:减库存
                $redis->hincrby('goods_seckill_1', 'seckill_num', -1);
                #步骤5:生成订单
                $pdo = new \PDO('mysql:dbname=php15shop', 'root', 'root');
    
                $userOrderInfo = explode('%', $orderInfoString); //$uid.'%'.$goods_id.'%'.$price
                    $order_id = date('Ymd').time().uniqid();
                    $total_price = $userOrderInfo[2];
                    $member_id = $userOrderInfo[0];
                    $goods_id = $userOrderInfo[1];
                    $create_time = time();
                    $update_time = time();
                    #主表(sh_order)
                    $sql = "insert into sh_order (order_id, total_price, member_id, create_time, update_time)
                    value 
                    ('{$order_id}', $total_price, $member_id, $create_time, $update_time)";
                    $pdo->exec($sql);
                    #从表(sh_order_goods)
                    $sql = "insert into sh_order_goods (order_id, goods_id, goods_number, goods_price) value('{$order_id}', $goods_id , $total_price, 2)";
                    $pdo->exec($sql);
                    
                echo "over...\n";
            }
        });
    };
    
    // 运行worker
    Worker::runAll();
    

    通过DOS窗口运行workerman.php文件,监听队列数据

    通过本地Apache安装目录下的bin目录ab测压工具测试并发

    ab -n 1000 -c 100 http://127.0.0.1/redis2/redis.php
    

    查看主表数据...

    搭建秒杀项目虚拟主机

    创建虚拟目录seckill

    将seckill项目解压到站点目录中

    打开seckill站点目录并修改数据库信息

    将之前的shop商城数据复制一份 创建新数据库并修改sh_goods表增加字段is_seckill(是否秒杀商品)

    在Admin后台创建Goods控制器seckillConfig方法

    //商品秒杀配置
    public function seckillConfig()
    {   
        #步骤2:加载视图
        return $this->fetch('');
    }
    

    创建视图文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>商品秒杀配置</title>
    </head>
    <body>
        <form action="" method="post">
            <p>秒杀开始时间<input type="text" name="start_time" /></p>
            <p>秒杀结束时间<input type="text" name="stop_time" /></p>
            <p>秒杀价格<input type="text" name="price" /></p>
            <p>秒杀数量<input type="text" name="num" /></p>
            <p><input type="submit" value="提交"></p>
        </form>
    </body>
    </html>
    

    数据处理:修改商品状态为秒杀 将秒杀商品数据保存的redis中

    redis键规则:
    hmset goods_seckill_1 start_time 0 stop_time 0 price 30 real_num 3 seckill_num 3
    
    <a href="{:url('admin/goods/seckillConfig', array('goods_id' => $list['goods_id']))}" 
    class="showContent tablelink">
        秒杀商品配置
    </a>
    

    修改admin后台的goods控制器sekillconfig方法进行数据处理

      public function seckillConfig()
        {   
            #步骤1:判断是否post提交
            if (request()->isPost()) {
                #步骤2:接受数据
                $start_time = input('start_time');
                $stop_time = input('stop_time');
                $price = input('price');
                $num = input('num');
                $goods_id = input('goods_id');
                #步骤3:插入数据
                $redis = new \Redis;
                $redis->connect('192.168.159.128', 6379);
                $redis->auth('123');
                #hmset 键  字段1 值1 ... 字段n 值n
                #hmset goods_seckill_1 start_time 0 stop_time 0 price 30 real_num 3 seckill_num 3
                $tempData = array(
                    'start_time' => $start_time,
                    'stop_time' => $stop_time,
                    'price' => $price,
                    'real_num' => $num,
                    'seckill_num' => $num,
                );
                $rs = $redis -> hMset('goods_seckill_'.$goods_id, $tempData);
                #步骤4:判断
                if ($rs) {
                    #修改商品状态为秒杀
                    Goods::where('goods_id', $goods_id)->update([
                        'is_seckill' => 1
                    ]);
                    #跳转到商品秒杀列表页
                    $this->success("商品秒杀配置成功", url("admin/goods/seckill"));
                }else{
                    $this->error("商品秒杀配置失败");
                }
            } else {
                #步骤2:加载视图
                return $this->fetch('');
            }
        }
    

    整合日期插件

    下载jq插件包放到查念public/plugin目录好

    商品秒杀页配置引入

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>商品秒杀配置</title>
    </head>
    <body>
        <form action="" method="post">
            <p>秒杀开始时间
                <input type="text" name="start_time" 
                    onClick="WdatePicker({el:this,dateFmt:'yyyy-MM-dd HH:mm:ss'})" 
                    autocomplete="off"
                />
            </p>
            <p>
                秒杀结束时间<input type="text" name="stop_time" 
                    onClick="WdatePicker({el:this,dateFmt:'yyyy-MM-dd HH:mm:ss'})" 
                    autocomplete="off"
                />
            </p>
            <p>秒杀价格<input type="text" name="price" /></p>
            <p>秒杀数量<input type="text" name="num" /></p>
            <p><input type="submit" value="提交"></p>
        </form>
    
        <script language="javascript" type="text/javascript" src="/plugin/My97DatePicker/WdatePicker.js"></script>
    </body>
    </html>
    

    修改控制器方法格式化日期

    start_time => strtltime($start_time)
    stop_time => strtotime($stop_time)
    

    在Admin后台创建Goods控制器seckill方法

    public function seckill()
    {
        #步骤1:查询所有数据
        $seckills = Goods::where('is_seckill', 1)->select();
        #步骤2:过滤数据
        foreach ($seckills as $seckill) {
            #$seckill->goods_id
            #$seckill->goods_name
            #查询商品秒杀信息
            $redis = new \Redis;
            $redis->connect('192.168.159.128', 6379);
            $redis->auth('123');
            $temp = $redis -> hMget('goods_seckill_'.$seckill->goods_id, array(
                'start_time',
                'stop_time',
                'price',
                'real_num'
            ));
            #将商品秒杀信息添加到$seckill中
            $seckill->start_time = $temp['start_time'];
            $seckill->stop_time = $temp['stop_time'];
            $seckill->price = $temp['price'];
            $seckill->real_num = $temp['real_num'];
        }
        #步骤3:加载视图
        return $this->fetch('', [
            'seckills'=>$seckills
        ]);
    }
    

    创建视图并循环显示数据

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        <table border="1" cellpadding="10" cellspacing="0">
            <tr>
                <td>商品ID</td>
                <td>商品名称</td>
                <td>商品价格</td>
                <td>商品数量</td>
                <td>开始时间</td>
                <td>结束时间</td>
                <td>距离结束</td>
            </tr>
            {foreach $seckills as $seckill}
            <tr>
                <td>{$seckill.goods_id}</td>
                <td>{$seckill.goods_name}</td>
                <td>{$seckill.price}</td>
                <td>{$seckill.real_num}</td>
                <td>{:date('Y-m-d H:i:s', $seckill.start_time)}</td>
                <td>{:date('Y-m-d H:i:s', $seckill.stop_time)}</td>
                <td>0</td>
            </tr>
            {/foreach}
        </table>
    </body>
    </html>
    

    距离倒计时(修改控制器 ) 增加字段

    $seckill->time = $temp['stop_time']
    

    距离倒计时(修改视图)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script type="text/javascript" src="{:config('admin_static')}/js/jquery.js"></script>
        <script>
        function timer(intDiff,idName) {
            window.setInterval(function() {
                var day = 0,
                    hour = 0,
                    minute = 0,
                    second = 0; //时间默认值     
                if (intDiff > 0) {
                    day = Math.floor(intDiff / (60 * 60 * 24));
                    hour = Math.floor(intDiff / (60 * 60)) - (day * 24);
                    minute = Math.floor(intDiff / 60) - (day * 24 * 60) - (hour * 60);
                    second = Math.floor(intDiff) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60);
                }
                if (minute <= 9) minute = '0' + minute;
                if (second <= 9) second = '0' + second;
    
                $(idName).html(day + "天" + hour + '时' + minute + '分' + second + '秒');
    
                //console.log(idName);
                //$(idName+' .day_show').html(day + "天");
                //$(idName+' .hour_show').html('<s id="h"></s>' + hour + '时');
                //$(idName+' .minute_show').html('<s></s>' + minute + '分');
                //$(idName+' .second_show').html('<s></s>' + second + '秒');
                intDiff--;
            }, 1000);
        }
        </script>
    </head>
    <body>
    
        <table border="1" cellpadding="10" cellspacing="0">
            <tr>
                <td>商品ID</td>
                <td>商品名称</td>
                <td>商品价格</td>
                <td>商品数量</td>
                <td>开始时间</td>
                <td>结束时间</td>
                <td>距离结束</td>
            </tr>
            {foreach $seckills as $seckill}
            <tr>
                <td>{$seckill.goods_id}</td>
                <td>{$seckill.goods_name}</td>
                <td>{$seckill.price}</td>
                <td>{$seckill.real_num}</td>
                <td>{:date('Y-m-d H:i:s', $seckill.start_time)}</td>
                <td>{:date('Y-m-d H:i:s', $seckill.stop_time)}</td>
                <td id="time{$seckill.goods_id}">0</td>
            </tr>
            <script>
             timer({$seckill->time},'#time{$seckill.goods_id}');
            </script>
            {/foreach}
        </table>
    </body>
    </html>
    

    完成前台秒杀功能

    修改home/index/index

    <li><a href="{:url('home/seckill/index')}">商品秒杀</a></li>
    

    在后台创建seckill控制器index方法

    <?php
    namespace app\home\controller;
    use think\Controller;
    use app\home\model\Goods;
    
    class SeckillController extends Controller
    {
        public function index()
        {
          #步骤1:查询所有数据
          $seckills = Goods::where('is_seckill', 1)->select();
          #步骤2:过滤数据
          foreach ($seckills as $seckill) {
              #$seckill->goods_id
              #$seckill->goods_name
              #查询商品秒杀信息
              $redis = new \Redis;
              $redis->connect('192.168.159.128', 6379);
              $redis->auth('123');
              $temp = $redis -> hMget('goods_seckill_'.$seckill->goods_id, array(
                  'start_time',
                  'stop_time',
                  'price',
                  'real_num',
                  'seckill_num',
              ));
              #将商品秒杀信息添加到$seckill中
              $seckill->start_time = $temp['start_time'];
              $seckill->stop_time = $temp['stop_time'];
              $seckill->price = $temp['price'];
              $seckill->real_num = $temp['real_num'];
              $seckill->seckill_num = $temp['seckill_num'];
          }
          #步骤3:加载视图
          return $this->fetch('', [
              'seckills'=>$seckills
          ]);
        }
    }
    

    创建视图并循环显示数据

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>一元秒杀!还包邮!</title>
    <style type="text/css">
    *{margin:0; padding:0;}
    body{font-family:"微软雅黑";}
    .html{ overflow:auto}
    .c{ clear:both;}
    a{text-decoration: NONE; color:#000; font-size:16px;}
    .bodybg{ background:url({:config('home_static')}/image/bg.jpg); width:100%;}
    .banner{ background:url({:config('home_static')}/image/banner.jpg) no-repeat center; height:542px;}
    .nav{ border:none;width:1000px;margin:0 auto;}
    .probg{ background:#f4ad1e; width:1000px; margin:0 auto; text-align:center;}
    .fl{ float:left; padding-left:9px; padding-top:9px;}
    .onebg{ width:321px; height:321px; background:#FFF;}
    .titlebg{ width:321px; height:33px; background:#FFF;}
    .buybg{ background:url({:config('home_static')}/image/pricebg.jpg); width:321px; height:70px;} 
    .foreprice{ font-size:17px; text-decoration:line-through; color:#FFF;float:left; padding-top:13px; padding-left:25px;}
    .button{ width:96px;height:70px;float:right; padding:0;}
    .ads{ width:1000px; height:326px; margin:0 auto;}
    .sm{ background:#FFF; width:100%; margin:0 auto;}
    .xize{ width:1000px; margin:0 auto; text-align:left; font-size:36px; font-weight:700; color:#9E051A;}
    .shuoming{ width:1000px; margin:0 auto; text-align:left; font-size:20px;color:#9E051A; line-height:35px;}
    </style>
    </head>
    
    <body>
        <div class="banner"></div>
        <div class="bodybg">
                <div class="nav"><img src="{:config('home_static')}/image/nav.jpg" width="1000" height="70"/></div>
                <div class="probg"> 
    
               {foreach $seckills as $seckill}
                 <!-- 单个商品循环-->
                  <div class="fl">
                    <div class="onebg"  >
                    <a href="javascript:void(0);" ><img src="{:config('home_static')}/image/1.jpg" width="321px;height:321px"/></a>
                    </div> 
                    <div class="titlebg">
                    <a href="javascript:void(0);" >{$seckill.goods_name}</a>
                    </div>
                    <div class="buybg" style="background: red;">
                    <div class="foreprice" style="text-decoration:none;">价格:{$seckill.price}</div>
                    <div class="foreprice" style="text-decoration:none;">库存:{$seckill.seckill_num}</div>
                    <div class="button">
                        <!-- <img src="{:config('home_static')}/image/button.jpg"/> -->
                        <input type="button" value="立即抢购"  style="margin-top:20px; width: 80px;height: 30px; cursor: pointer;" data-id="{$seckill.goods_id}"/>
                      </div>        
                    </div>
                  </div>
                  <!-- 单个商品循环结束-->
               {/foreach}
    
                  <div>
                    <a href="javascript:void(0);" ><img src="{:config('home_static')}/image/ads.jpg" /></a>
                  </div>
                  
                  <div class="c"></div>                      
             </div> 
             <div class="sm">
             <br />
                 <div class="xize">1元秒杀细则:</div>
                <div class="shuoming">1.参与秒杀前,请详细阅读秒杀规则,凡参与1元秒杀活动的用户,均视为同意秒杀规则。<br />2.秒杀商品将于2014年7月7日08:00:00上线-2014年7月11日23:59:59结束,当天商品售罄时当天秒杀结束,活动期间每一个云中央注册会员每期仅限秒杀一个商品,秒杀多件成功者,并通过收货人及联系方式可判定为同一人的,则取消全部订单。<br />3.秒杀成功以支付成功为准,早秒早得;秒杀下单后30分钟内未付款者自动取消订单,请特别注意。<br />4.请确保秒杀填写的收货人信息真实有效,因联系方式填写错误导致未收到礼品的,由用户自行承担损失。<br />5.对于任何通过不正当手段参与秒杀者,不正当手段包括但不限于使用秒杀器或类似作弊软件,云中央网站有权依据自身技术判断,并在不事先通知的情况下取消其秒杀资格或者取消订单。
                 </div>
             </div>
        </div>
    </body>
    </html>
    

    立即抢购(入队)

    在home/平台创建Seckill控制器创建add方法

    public function add()
    {
        #步骤1:接受数据
        $uid = session('member_id');
        if(!$uid) {
          echo json_encode(array('state' => 0, 'msg'=>'请登录后重试...'));
          die;
        };
        $goods_id = input('goods_id');
        if(!$goods_id) {
          echo json_encode(array('state' => 0, 'msg'=>'非法操作...'));
          die;
        }
    
        #步骤2:连接Redis
        $redis = new \Redis;
        $redis->connect('192.168.159.128', 6379);
        $redis->auth('123');
        $redis->select(0);
    
        #步骤3:过滤(判断时间和库存)
        //获取商品信息
        $goodsInfo = $redis->hmget("goods_seckill_{$goods_id}", array(
          'start_time', 'stop_time', 'real_num', 'seckill_num', 'price'
        ));
        if(!$goodsInfo) {
          echo json_encode(array('state' => 0, 'msg'=>'秒杀商品不存在...'));
          die;
        }
        //判断是否开始
        if ($goodsInfo['start_time'] > time()) {
          echo json_encode(array('state' => 0, 'msg'=>'未开始'));
          die;
        }
        //判断是否结束
        if ($goodsInfo['stop_time'] < time()) {
          echo json_encode(array('state' => 0, 'msg'=>'已结束'));
          die;
        }
        //判断库存
        if ($goodsInfo['seckill_num'] < 1) {
          echo json_encode(array('state' => 0, 'msg'=>'对不起,宝贝已被抢完!'));
          die;
        }
    
        #步骤4:将用户请求加入消息队列中
        $len = $redis->lpush("goods_seckill_{$goods_id}_rs", $uid.'%'.$goods_id.'%'.$goodsInfo['price']);
    
        #步骤5:判断库存(规则:队列中前n个抢购成功)
        if ($len > $goodsInfo['real_num']) {
        //抢购失败(队列长度 > 库存)
          echo json_encode(array('state' => 0, 'msg'=>'对不起,宝贝已被抢完!'));
          die;
        } else {
        //抢购成功,减库存(注:千万不能直接操作mysql因为有并发限制)
          echo json_encode(array('state' => 1, 'msg'=>'秒杀成功'));
          die;
        }
    }
    

    修改秒杀列表发送异步请求

    <script type="text/javascript" src="{:config('home_static')}/js/jquery-1.8.3.min.js"></script>
    <script> 
    $(function(){
        $('.buybg input').click(function(){
             //禁用按钮
             var thisObj = $(this);
             $(this).attr('disabled', 'disabled');
             $(this).css('cursor', 'default');
             $(this).val('抢购中...');
    
             var goods_id = $(this).attr('data-id');
             $.post("{:url('home/seckill/add')}", {goods_id:goods_id},function(data){
                  if (data.state) {
                    alert(data.msg);
                    location.href = "{:url('home/order/seckill')}";
                  } else {
                    alert(data.msg)
                  }
    
                  //还原按钮
                 $(thisObj).removeAttr('disabled');
                 $(thisObj).css('cursor', 'pointer');
                 $(thisObj).val('立即抢购');
    
             }, 'json');
    
        });
    });
    </script>
    

    使用Workerman框架(出队)

    <?php
    use \Workerman\Worker;
    use \Workerman\Lib\Timer;
    require_once __DIR__ . '/Workerman/Autoloader.php';
    
    $task = new Worker();
    // 开启多少个进程运行定时任务,注意业务是否在多进程有并发问题
    $task->count = 1;
    $task->onWorkerStart = function($task)
    {
        //每0.1秒执行一次(精度可以达到毫秒0.001)
        $time_interval = 0.1; 
        Timer::add($time_interval, function()
        {
            $goods_id = 9;
            #步骤1:连接Redis
            $redis = new Redis;
            $redis->connect('192.168.159.128', 6379);
            $redis->auth('123');
            $redis->select(0);
            #步骤2:获取秒杀相关信息
            $allowBuyNum = $redis->hget("goods_seckill_{$goods_id}", 'seckill_num'); //秒杀剩余库存
            $orderInfoString = $redis->rpop("goods_seckill_{$goods_id}_rs");                     //队列抢购用户信息
            #步骤3:判断(有库存 && 有人抢购)
            if($allowBuyNum > 0 && $orderInfoString) 
            {
                echo "allowBuyNum:$allowBuyNum\n";
    
                #步骤4:减库存
                $redis->hincrby('goods_seckill_1', 'seckill_num', -1);
                #步骤5:生成订单
                $pdo = new \PDO('mysql:dbname=seckill', 'root', 'root');
    
                $userOrderInfo = explode('%', $orderInfoString); //$uid.'%'.$goods_id.'%'.$price
                    $order_id = date('Ymd').time().uniqid();
                    $total_price = $userOrderInfo[2];
                    $member_id = $userOrderInfo[0];
                    $goods_id = $userOrderInfo[1];
                    $create_time = time();
                    $update_time = time();
                    #主表(sh_order)
                    $sql = "insert into sh_order (is_seckill, order_id, total_price, member_id, create_time, update_time)
                    value 
                    (1, '{$order_id}', $total_price, $member_id, $create_time, $update_time)";
                    $pdo->exec($sql);
                    #从表(sh_order_goods)
                    $sql = "insert into sh_order_goods (order_id, goods_id, goods_number, goods_price) value('{$order_id}', $goods_id, 1, $total_price)";
                    $pdo->exec($sql);
                    
                echo "over...\n";
            }
        });
    };
    
    // 运行worker
    Worker::runAll();
    

    相关文章

      网友评论

        本文标题:NoSQL(Redis)秒杀

        本文链接:https://www.haomeiwen.com/subject/jdkbuftx.html