Redis分布式锁--PHP

Redis 分布式锁的作用

在单机环境下,有个秒杀商品的活动,在短时间内,服务器压力和流量会陡然上升。这个就会存在并发的问题。想要解决并发需要解决以下问题

1、提高系统吞吐率也就是qps 每秒处理的请求书
2、避免商品在高并发的情况下,出现资源争抢导致的超买超买问题

解决问题一:采用内存型数据库提高系统的qps
解决问题二:就要用到经常会遇到的锁,例如MySQL 有读锁、写锁、排他锁、悲观锁、乐观锁。不过这里只讨论redis来实现锁

简单版设置锁

$redis = new Redis();
$redis->connect('127.0.0.1', 6379); //连接Redis

$expire = 10;//有效期10秒
$key = 'lock';//key
$value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期
$lock = $redis->setnx($key, $value);
//判断是否上锁成功,成功则执行下步操作
if(!empty($lock))
{
//下步操作...       
}

如果以这样的简单版设置锁就能解决所有问题,未免也太小看在程序中应用了。

按正常的操作示例基本上都是这样写的。但是这样写有一些问题

1、假如有10000 个请求访问了redis 不存在的键,这样请求就是指接到了MySQL数据,造成CPU短时间内达到100%甚至宕机。这样场景俗称缓存击穿造成的缓存雪崩。
2、 假如CPU过高或者网络延时问题,造成锁没有被删除掉或者缓存键过期没有被回收的情况,就会形成死锁。

解决问题:引用reids setnx 方法的作用是,当设置的key 不存在时,设置新的值。这样就避免了缓存击穿的问题。检测键的过期时间,避免产生死锁

解决死锁问题

    $expire = 10;//有效期10秒
	$key = 'lock';//key
	$value = time() + $expire;//锁的值 = Unix时间戳 + 锁的有效期
	$status = true;
	while($status)
	{
	    $lock = $redis->setnx($key, $value);
	    if(empty($lock))
	    {
	        $value = $redis->get($key);
	        if($value < time())
	        {
	            $redis->del($key);
	        }       
	    }else{
	        $status = false;
	        //下步操作....
	    }
	}

1、按理说这样解决单机版锁竞争问题已经是没有多大问题了。那么特殊情况来了,如果这个设置锁的键由于意外情况没有被删除,这样同样会有死锁的情况发生。

2、分布式集群业务业务场景下,每台服务器是独立存在的。多台服务器怎么通过一个标识来相互竞争锁呢。这里就用到了分布式锁

这里简单介绍一下,以MYSQL 的事务机制来延生。事务四个特性ACID,有四种隔离级别:未提交读、已提交读、可重复读、串行化。这些特性都只在单台服务器上生效。到了分布式集群了,数据在不同的服务器上,紧靠事务很难保持数据的一致性及隔离性,事务的作用就意义不大了。Redis也是如此。

正确的分布式锁的打开方式

   /**
	 * 实现Redis分布锁
	 */
	$key        = 'demo';       //要更新信息的缓存KEY
	$lockKey    = 'lock:'.$key; //设置锁KEY
	$lockExpire = 10;           //设置锁的有效期为10秒
	 
	//获取缓存信息
	$result = $redis->get($key);
	//判断缓存中是否有数据
	if(empty($result))
	{
	    $status = TRUE;
	    while ($status)
	    {
	        //设置锁值为当前时间戳 + 有效期
	        $lockValue = time() + $lockExpire;
	        /**
	         * 创建锁
	         * 试图以$lockKey为key创建一个缓存,value值为当前时间戳
	         * 由于setnx()函数只有在不存在当前key的缓存时才会创建成功
	         * 所以,用此函数就可以判断当前执行的操作是否已经有其他进程在执行了
	         * @var [type]
	         */
	        $lock = $redis->setnx($lockKey, $lockValue);
	        /**
	         * 满足两个条件中的一个即可进行操作
	         * 1、上面一步创建锁成功;
	         * 2、   1)判断锁的值(时间戳)是否小于当前时间    $redis->get()
	         *      2)同时给锁设置新值成功    $redis->getset()
	         */
	        if(!empty($lock) || ($redis->get($lockKey) < time() && $redis->getSet($lockKey, $lockValue) < time() ))
	        {
	            //给锁设置生存时间
	            $redis->expire($lockKey, $lockExpire);
	            //******************************
	            //此处执行插入、更新缓存操作...
	            //******************************
	 
	            //以上程序走完删除锁
	            //检测锁是否过期,过期锁没必要删除
	            if($redis->ttl($lockKey))
	                $redis->del($lockKey);
	            $status = FALSE;
	        }else{
	            /**
	             * 如果存在有效锁这里做相应处理
	             *      等待当前操作完成再执行此次请求
	             *      直接返回
	             */
	            sleep(2);//等待2秒后再尝试执行操作
	        }
	    }
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值