php+redis消息队列抢购实现

实现功能:
1. 基于redis队列,防止高并发的超卖
2. 基于mysql的事务加排它锁,防止高并发的超卖

基于redis队列工作流程:
1. 管理员根据goods表中的库存,创建redis商品库存队列
2. 客户端访问秒杀API
3. web服务器先从redis的商品库存队列中查询剩余库存重点内容
4. redis队列中有剩余,则在mysql中创建订单,去库存,抢购成功
5. redis队列中没有剩余,则提示库存不足,抢购失败重点内容

基于mysql事务和排它锁工作流程:
1:开启事务
2:查询库存,并显示的设置写锁(排他锁):SELECT * FROM goods WHERE id = 1 FOR UPDATE
3:生成订单
4:去库存,隐示的设置写锁(排他锁):UPDATE goods SET counts = counts – 1 WHERE id = 1
5:commit,释放锁
注意:第二步步可以设置共享锁,不然有可能会造成死锁。


<?php
/**********************************************
* 抢购模块
*
* @author liubin
* @date 2016-02-10
*
* ab -n 1000 -c 100 http://192.168.16.73/Seckill/buy.php
*
*/
class seckill extends common
{

    private $_orderModel = null;
    private $_goodsModel = null;
    private $_redis = null;
    /*
     * 错误信息
    */
    protected $_error = '';
    /**
     * 构造器
     *
    */
    public function __construct()
    {
        if($this->_orderModel === null){
            $this->_orderModel = new OrderModel();
        }
        if($this->_goodsModel === null){
            $this->_goodsModel = new GoodsModel();
        }
        if($this->_redis === null){
            $this->_redis = new QRedis(); 
        }
    }
    /*
     * 秒杀API
     * 
     * @author liubin
     * @date 2017-02-10
    */
    public function addQsec(){
        $gid 	= intval($_GET['gid']);
        $type	= isset($_GET['type']) ? $_GET['type'] : 'mysql';
        switch ($type) {
            case 'mysql':
                $this->order_check_mysql($gid);
                echo $this->getError();
                break;
            case 'redis':
                $this->order_check_redis($gid);
                echo $this->getError();
                break;
            case 'transaction':
                $this->order_check_transaction($gid);
                echo $this->getError();
                break;
            default:
                echo '类型错误';
                break;
        }
    }
    /*
     * 获取错误信息
     * 
     * @author liubin
     * @date 2017-02-10
    */
    public function getError(){
        return $this->_error;
    }
    /*
     * 基于mysql验证库存信息
     * @desc 高并发下会导致超卖
     *
     * @author liubin
     * @date 2017-02-10
    */
    protected function order_check_mysql($gid){


        $model	= $this->_goodsModel;
        $pdo	= $model->getHandler();
        $gid	= intval($gid);

        /*
         * 1:$sql_forlock如果不加事务,不加写锁:
         * 超卖非常严重,就不说了
         * 
         * 2:$sql_forlock如果不加事务,只加写锁:
         * 第一个会话读$sql_forlock时加写锁,第一个会话$sql_forlock查询结束会释放该行锁.
         * 第二个会话在第一个会话释放后读$sql_forlock的写锁时,会再次$sql_forlock查库存
         * 导致超卖现象产生
         *
        */
        $sql_forlock	= 'select * from goods where id = '.$gid .' limit 1 for update';
        //$sql_forlock	= 'select * from goods where id = '.$gid .' limit 1';
        $result			= $pdo->query($sql_forlock,PDO::FETCH_ASSOC);
        $goodsInfo		= $result->fetch();

        if($goodsInfo['counts']>0){

            //去库存
            $gid 	= $goodsInfo['id'];
            $sql_inventory	= 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
            $result = $this->_goodsModel->exect($sql_inventory);
            if($result){
                //创订单
                $data              = [];
                $data['order_id'] 	= $this->_orderModel->buildOrderNo();
                $data['goods_id'] 	= $goodsInfo['id'];
                $data['addtime']   = time();
                $data['uid']       = 1;
                $order_rs 			= $this->_orderModel->create_order($data);
                if($order_rs){
                    $this->_error = '购买成功';
                    return true;
                }
            }
        }

        $this->_error = '库存不足';
        return false;

    }
    /*
     * 基于redis队列验证库存信息
     * @desc Redis是底层是单线程的,命令执行是原子操作,包括lpush,lpop等.高并发下不会导致超卖
     *
     * @author liubin
     * @date 2017-02-10
    */
    protected function order_check_redis($gid){
        $goodsInfo 	= $this->_goodsModel->getGoods($gid);
        if(!$goodsInfo){
            $this->_error = '商品不存在';
            return false;
        }
        $key 	= 'goods_list_'.$goodsInfo['id'];
        $count 	= $this->_redis->getHandel()->lpop($key);
        if(!$count){
            $this->_error = '库存不足';
            return false;
        }
        //生成订单
        $data              = [];
        $data['order_id'] 	= $this->_orderModel->buildOrderNo();
        $data['goods_id'] 	= $goodsInfo['id'];
        $data['addtime']   = time();
        $data['uid']       = 1;
        $order_rs 			= $this->_orderModel->create_order($data);

        //库存减少
        $gid 	= $goodsInfo['id'];
        $sql	= 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
        $result = $this->_goodsModel->exect($sql);
        $this->_error = '购买成功';
        return true;
    }
    /*
     * 基于mysql事务验证库存信息
     * @desc 事务 和 行锁 模式,高并发下不会导致超卖,但效率会慢点
     * @author liubin
     * @date 2017-02-10


     说明:
     如果$sql_forlock不加写锁,并发时,$sql_forlock查询的记录存都大于0,可以减库存操作.
     如果$sql_forlock加了写锁,并发时,$sql_forlock查询是等待第一次链接释放后查询.所以库存最多就是5

    */
    protected function order_check_transaction($gid){

        $model	= $this->_goodsModel;
        $pdo	= $model->getHandler();
        $gid	= intval($gid);

        try{
            $pdo->beginTransaction();//开启事务处理




            /*
             * 1:$sql_forlock如果只加事务,不加写锁:
             * 开启事务
             * 因为没有加锁,读$sql_forlock后,并发时$sql_inventory之前还可以再读。
             * $sql_inventory之后和commit之前才会锁定
             * 出现超卖跟事务的一致性不冲突
             * 
             *
             * 2:$sql_forlock如果加了事务,又加读锁:
             * 开启事务
             * 第一个会话读$sql_forlock时加读锁,并发时,第二个会话也允许获得$sql_forlock的读锁,
             * 但是在第一个会话执行去库存操作时(写锁),写锁便会等待第二个会话的读锁,第二个会话执行写操作时,写锁便会等待第一个会话的读锁,
             * 出现死锁

             * 3:$sql_forlock如果加了事务,又加写锁:
             * 开启事务
             * 第一个会话读$sql_forlock时加写锁,直到commit才会释放写锁,并发查询不会出现超卖现象。
             *
            */

            $sql_forlock		= 'select * from goods where id = '.$gid .' limit 1 for update';
            //$sql_forlock		= 'select * from goods where id = '.$gid .' limit 1 LOCK IN SHARE MODE';
            //$sql_forlock		= 'select * from goods where id = '.$gid .' limit 1';
            $result		= $pdo->query($sql_forlock,PDO::FETCH_ASSOC);
            $goodsInfo	= $result->fetch();

            if($goodsInfo['counts']>0){

                //去库存
                $gid 			= $goodsInfo['id'];
                $sql_inventory	= 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
                $result			= $this->_goodsModel->exect($sql_inventory);

                if(!$result){
                    $pdo->rollBack();
                    $this->_error = '库存减少失败';
                    return false;
                }

                //创订单
                $data              = [];
                $data['id']            = 'null';
                $data['order_id'] 	= $this->_orderModel->buildOrderNo();
                $data['goods_id'] 	= $goodsInfo['id'];
                $data['uid']       = 'abc';
                $data['addtime']   = time();

                $sql = 'insert into orders (id,order_id,goods_id,uid,addtime) values ('.$data['id'].',"'.$data['order_id'].'","'.$data['goods_id'].'","'.$data['uid'].'","'.$data['addtime'].'")';          
                $result = $pdo->exec($sql);
                if(!$result){
                    $pdo->rollBack();
                    $this->_error = '订单创建失败';
                    return false;
                }
                $pdo->commit();//提交
                $this->_error = '购买成功';
                return true;

            }else{
                $this->_error = '库存不足';
                return false;
            }
        }catch(PDOException $e){
            echo $e->getMessage();
            $pdo->rollBack();
        }




    }
    /*
     * 创建订单
     * mysql 事物处理,也可以用存储过程
     *
    */
    private function create_order($goodsInfo){
        //生成订单
        $data              = [];
        $data['order_id'] 	= $this->_orderModel->buildOrderNo();
        $data['goods_id'] 	= $goodsInfo['id'];
        $data['addtime']   = time();
        $data['uid']       = 1;
        $order_rs 			= $this->_orderModel->create_order($data);

        //库存减少
        $gid 	= $goodsInfo['id'];
        $sql	= 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
        $result = $this->_goodsModel->exect($sql);
        return true;
    }
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值