php redis 布式锁 共享session 秒杀 【合辑整理】

3 篇文章 0 订阅
  • 1 redis 实现分布式锁。 悲观锁。

原理,进去则抢锁,抢失败了 等一秒再抢,再等一秒再抢,如此反复循环。

解锁就是删掉对应的键。  

/**
 * 实现redis 悲观锁
 * User: babytuo
 */
Class RedisLock {

    public $expire = 2;

    public function test(){

         $this->lock("test1");
         echo "111";
    }


    public function lock($key){

        $redis = self::createRedisObj();
        $now = time();

        /**  理解setnx
        若给定的 key 已经存在,则 SETNX 不做任何动作。
        SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
         */
        //抢锁.
        $isLock = $redis->setnx($key , time() + $this->expire);  //设为过期时间.

        //不成功
        while ( ! $isLock) {
            $now++;
            $time = $now + $this->expire;

            //再重新创建
            $lock = $redis->setnx($key, $time);
            if ($lock == 1 || ($now > $redis->get($key) && $now > $redis->getset($key, $time))) {//争锁成功后,设置新的过期时间.
                break;
            } else {
                sleep(1);//0.5s
            }
        }
        return true;
    }

    /**
     * 解锁
     * @param type $flag
     * @return boolean
     */
    public function unlock($key) {
        $redis = self::createRedisObj();
        $redis->del($key);
        return true;
    }

    /**
     * 检查锁是否存在
     * @param $key
     * @return bool
     */
    public function checklock($key){
        $redis = self::createRedisObj();
        return $redis->exists($key);
    }

    public static $_redis;
    /**
     * 创建一个redis 对象.
     * @return Redis
     */
    public static function createRedisObj(){
        if( ! self::$_redis){
            $redis = new Redis();
            $info =  Yii::app()->cache->servers[0];  //读取配置
            $host = $info["host"];
            $port = $info["port"];
            $redis->connect($host,$port);
            $redis_db =    Yii::app()->settings->get('system' , 'redis_db');  //设置默认库
            $redis->select($redis_db);
            self::$_redis = $redis;
        }
        return self::$_redis;
    }
}

代码详解:

 setnx()命令:

setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。

该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。

 get()命令:
get(key) 获取key的值,如果存在,则返回;如果不存在,则返回nil;
 getset()命令:
  这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
假设key原来是不存在的,那么多次执行这个命令,会出现下边的效果:
1. getset(key, "value1")  返回nil   此时key的值会被设置为value1
      2. getset(key, "value2")  返回value1   此时key的值会被设置为value2
      3. 依次类推!
二.具体的使用步骤如下:
     1. setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。
     2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。
     3. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。
     4. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

     5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。


 2 redis 共享session

//进入php.ini 配置文件,进行如下设置。
[Session]
; Handler used to store/retrieve data.
; http://php.net/session.save-handler
;session.save_handler = files
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?database=5"
  • 3 redis 实现秒杀功能。
//3.1一点点准备工作,两张表格。商品表+订单表。  以及数据初始化。


CREATE TABLE `sec_goods` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `stock` int(11) unsigned DEFAULT '1' COMMENT '放入库存',
  `title` varchar(32) DEFAULT NULL COMMENT '商品名称',
  `stock_avail` int(11) DEFAULT '1' COMMENT '剩余可售',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;



CREATE TABLE `sec_order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sn` varchar(64) NOT NULL COMMENT '订单号',
  `goods_id` int(11) DEFAULT NULL COMMENT '商品编号',
  `user_id` int(11) DEFAULT NULL COMMENT '用户编号',
  `create_at` int(11) DEFAULT NULL COMMENT '创建时间',
  `num` int(11) DEFAULT '0' COMMENT '成交数量',
  PRIMARY KEY (`id`),
  UNIQUE KEY `sn` (`sn`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8


//初始化数据
$sql = "TRUNCATE sec_goods"; Yii::app()->db->createCommand($sql)->execute(); $sql = "TRUNCATE sec_order"; Yii::app()->db->createCommand($sql)->execute(); 
        //准备五件商品,每件100 份
        for($i = 1;$i<6;$i++){
            $sql = "INSERT INTO sec_goods SET title = 'test_goods_$i' ,stock = 100,stock_avail = 100";
            $bool = Yii::app()->db->createCommand($sql)->execute();
            if( !$bool ){ throw new Exception("执行失败".$sql); }
        }

        echo "data init succ!";
//3.2  redis 数据准备。   

//初始化redis 对象。  
    $redis = self::createRedisObj();

//有多少件商品,往队列里写多少条记录,使用 pop 的原子性,一次仅能取出一条。
        $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_avail_".$goods_id;
            for($i =0 ; $i< $stock_avail; $i++){
                $redis->lpush($redis_key , 1);
            }
            echo $goods_id."llen is ".$redis->lLen($redis_key)."<br/>";
        endforeach;
//3.3 执行购买。
//模拟用户购买参数
        $uid = rand(1,10);
        $amount = rand(1,5);
        $goods_id = rand(1,6);
        $time = time();

//用redis 来验证是否卖光。
        $redis = BusinessHelper::createRedisObj();
        $redis_key = "goods_avail_".$goods_id;

        $len = $redis->lLen($redis_key);
        if( $len == 0 ){
            exit("抢光了!");
        }else if( $len < $amount){
            exit("库存不足!");
        }

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


   //执行购买操作。
        $sql = "select stock_avail from sec_goods where id = $goods_id";
        $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); }

        }

最后,使用 apache 的 ab 压一下。

//- n 请求多少次。  -c 单次多少个并发。

ab -c 100 -n 10000  www.demo.com/test  

 

备注,这里再描述下redis 秒杀。
1 初始化商品数据。
2 将商品数据,按库存量写到队列中去。  例如编号1 商品有10件。   就往 goods_1  队列里写10个1  进去。
3 下单时,例用redis pop 的原子性。

为了篇幅简洁,有些地方略掉。(例如,用户购买失败时,需要重新压回队列 。 购买代码,需要写在一个事务里等。。。)







                
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值