资讯专栏INFORMATION COLUMN

PHP+Redis并发下单(代码全篇)

CKJOKER / 322人阅读

摘要:连接信息可以用原生也可以用其它的框架集成锁定默认过期时间避免死锁新版已经集成了大多数集成操作解锁业务相关的可以是库存物品数等用户相关的下单下单个数用户场次是为了方便异常处理方便数据查找商品场次此方法不具备原子

ip = $config["ip"];
            }
            if(isset($config["port"])){
                $this->ip = $config["port"];
            }
        }
        /**
         * Redis连接信息可以用原生,也可以用其它的框架集成
         */
        $this->_redis = new Redis();
        $this->_redis->connect($this->ip,$this->port);

    }


    /** 锁定
     * @param int $intTimeout 默认过期时间(避免死锁)
     * @return bool
     */
    private function lock($intTimeout = 8) {
        #新版set,已经集成了大多数集成操作
        $strRet   = $this->_redis->set($this->_lockKey, time().rand(10000,99999).rand(1000,9999).rand(100,999), "ex", $intTimeout, "nx");
        if($strRet) {
            return true;
        }else{
            return false;
        }
    }


    /** 解锁
     * @throws Exception
     */
    private function unlock()
    {
        $strRet   = $this->_redis->del($this->_lockKey);
        if($strRet) {
            return true;
        }else{
            if($this->_redis->get($this->_lockKey)) {
              return false ;
            }else{
              return false ;
            }
        }
    }

    /**
     * 业务相关的key,可以是库存,物品数等
     */
    const ORDER_KEY = "order_num";

    /**
     * 用户相关的key
     */
    const USER_KEY = "user_num";

    /** Redis下单
     * @param int $num 下单个数
     * @param string $userId 用户ID
     *
     * 场次是为了方便异常处理,方便数据查找
     * @param string $bout 商品场次 => order_num:1 , order_num:2
     * @return bool
     * @throws Exception
     */
     public function order( string $userId ,string $bout = "1" ,int $num = 1)
    {
        $orderKey = self::ORDER_KEY.":".$bout ;
        $userKey  = self::USER_KEY.":".$bout ;
        //此方法不具备原子性 并发处理是不能做条件判断
        //$len = $this->_redis->llen();
        #实际为n+1次触发完结,这里只做Redis自减
        $check = $this->_redis->lpop($orderKey);
        if(!$check){
            #当前order_num已经为0!
            //自动补货为 100 ,$bout有一定的处理规则,不能乱传
            self::autoBuild(100,$bout);
            return false ;
        }
        //特殊处理,避免n+1次的情况
        $len = $this->_redis->llen($orderKey) ;
        if($len == 0) {
            //自动补货为 100 ,$bout有一定的处理规则,不能乱传
            self::autoBuild(100,$bout);
            return false ;
        }
        //添加用户数据
        $result = $this->_redis->lpush($userKey,$userId);
        if($result){
            return true ;
        }else{
            return false ;
        }
    }


    /** 失败处理
     * #增加当前库存
     * #减少用户库存
     * @param string $num
     * @param string $userId
     * @param $bout
     * @return bool
     * @throws Exception
     */
    public function _out(string $num,string $userId,$bout)
    {
        #并发参与时,总库存有5个,一共10次请求,成功5次,退款1次,实际库存1次
        #失败处理时和_buildOrder加上同一把锁,避免更新下次库存时,上次库存累积
        #_out 和 _buildOrder 同时只能有一个在执行,不然锁会报错,也避免下不必要的死锁
        self::lock();
        //减用户库存
        $user = $this->_redis->lpop(self::USER_KEY.":".$bout);
        if(!$user) {
           return false ;
        }
        //增加商品库存
        $all  = $this->_redis->lpush(self::ORDER_KEY.":".$bout,$userId);
        if(!$all) {
           //TODO::这里需要做容错处理,即再商品库存增加失败时,做记录
           return false ;
        }
        self::unlock();
    }


    /** 自动构建
     * @param int $num
     * @param $bout
     * @throws Exception
     */
    private  function autoBuild( int $num ,$bout)
    {
        $a = $this->_redis->get(self::ORDER_KEY.":".$bout);
        if(!$a) {
            //库存已完结
            $this->_buildOrder(self::ORDER_KEY.":".$bout,$num);
        }
    }


    /** 物品库存规则
     * @param $orderKey
     * @param $num
     * @return string
     * @throws Exception
     */
    private function _buildOrder($orderKey,$num)
    {
        //锁定
        self::lock();
        $ckNum = "0" ;#Redis操作后返回为string类型
        #总数 与$ckNum要相同类型 不然可能会出现判断错误
        if($num < 0) {
            throw new Exception("商品数量错误!");
        }
        $beforeNum = 0 ;
        //上一次库存判断 ()
        if($beforeNum > 0) {
            throw new Exception("商品未售罄!");
        }
        //当前库存判断
        $length = $this->_redis->llen($orderKey);
        if($length > 0) {
            throw new Exception("商品已经存在!");
        }
        //生成当前库存
        while ($ckNum < $num) {
            if($ckNum == $num) {
                break ;
            }else if($ckNum > $num){
                break ;
            }else{
                $ckNum = $this->_redis->lpush($orderKey,1) ;
                if($ckNum >=$num) {
                    break ;
                }
            }
        }
        //并发时 循环成功 redis不一定成功
        /*for ($i=1;$i<=$num ;$i++) {
            $ckNum = $this->_redis->lpush(self::$_allCoin.self::getNum().":".$coin,1);
            if($ckNum >= $num) {
                break ;
            }
        }*/
        //解锁
        self::unlock();
        return $ckNum ;
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30998.html

相关文章

  • 秒杀系统优化方案之缓存、队列、锁设计思路

    摘要:一为什么难秒杀系统难做的原因库存只有一份,所有人会在集中的时间读和写这些数据。又例如抢票,亦与秒杀类似,瞬时流量更甚。 一、为什么难     秒杀系统难做的原因:库存只有一份,所有人会在集中的时间读和写这些数据。例如小米手机每周二的秒杀,可能手机只有1万部,但瞬时进入的流量可能是几百几千万。又例如12306抢票,亦与秒杀类似,瞬时流量更甚。 主要需要解决的问题有两个: 高并发对数据库...

    dinfer 评论0 收藏0
  • PHP依赖注入(代码全篇)

    摘要:依赖注入传统的思路应用程序用到一个类就会创建类并调用类的方法。这样你可以完全控制依赖关系,通过调整不同的注入对象,来控制程序的行为。例如类用到了,可以在不修改类代码的情况下,改用。 依赖注入 传统的思路 应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法。 假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法。 而这个方法内需要一个Bim类,就会创建Bim...

    felix0913 评论0 收藏0
  • PHP面试题

    摘要:质量高在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。代码块捕获异常,并创建一个包含异常信息的对象。这样可以解决超卖的问题,但是会导致文件得开销很大。 6.你们公司是使用什么框架? 答:我们公司采用的是TP框架,运用的mysql+apache+php进行开发,因为TP框架是一个免费开源的,轻量级的php开发框架,而且是我们中国人自己开发的,...

    ls0609 评论0 收藏0
  • PHP 并发场景的几种解决方案

    摘要:在秒杀,抢购等并发场景下,可能会出现超卖的现象,在语言中并没有原生提供并发的解决方案,因此就需要借助其他方式来实现并发控制。借助文件排他锁,在处理下单请求的时候,用锁定一个文件,成功拿到锁的才能处理订单。 在秒杀,抢购等并发场景下,可能会出现超卖的现象,在PHP语言中并没有原生提供并发的解决方案,因此就需要借助其他方式来实现并发控制。 列出常见的解决方案有: 使用队列,额外起一个进程...

    neu 评论0 收藏0

发表评论

0条评论

CKJOKER

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<