Redis 分布式锁

August 21, 2019 · PHP · 数据库 · 220次阅读

今天小弟来谈谈Redis分布式锁的实现吧

首先呢先谈谈,为什么要锁,这锁是干嘛的呢,为什么要用锁呢?其实锁是一种可以封锁资源的东西。这种资源通常是共享的,通常会发生使用竞争的。

打个比方啊,如果你们公司就一个厕所一个坑位,而且还不带锁的,这样你上厕所的时候,别人也进去了,这种情况多尴尬啊,然后两个人共用一个坑位吗,当然是不行的啦。那该怎么办呢,这时候就用上锁这东西了,只要装上锁,这种情况后面的人只能等你用完以后把锁打开才能进去吧。当然数据也一样,当两个人同时操作一条数据的时候,就会出现这样的问题,他们会相互竞争去修改这条数据,这样的情况下就很难保证数据的可靠性了,常出现的场景比如,抢购,秒杀,集体抽奖等等,需要竞争共享资源的时候,平时的话可能你写代码跑起来发现没问题,可是一到请求量大的时候就有问题了。

而锁呢有两种一种单机锁,一种分布式锁

单机锁:
在编码的时候,为了保护共享资源,使得多线程环境下,不会出现“不好的结果”。我们可以使用锁来进行同步。于是我们可以根据具体的情况,使用悲观锁(比如文件方式的排它锁)来锁住一段代码。这段代码就会在没解锁的情况下阻塞,这样的话,只有等别人执行完解了锁才能继续。

不过呢今天要说的是redis的分布式锁,因为单机锁的话只能在单台服务器上用,如果你做了复杂均衡,那么就GG了,因为你单机锁的只能在当前的机器有用,你负载均衡多台了,这咋整,只能在它们最外层加个锁了,所以我还是整个分布式锁吧。

首先呢首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下几个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。(要是每个人人都有了,那坑位又得挤满人了)
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。(死锁的话就GG了就是厕所锁坏)
  3. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。

先贴下代码吧


<?php
class Lock{

protected $value; //用于标识哪个客户端的
function __construct($redis)
{
    $this->redis = $redis;
}

/**
 * Notes: 加锁的方法
 * Date: 2019/8/21
 * Time: 17:07
 * Author: zhangyizhao
 * @param string $key 加锁的场景值 如秒杀场景 aaa  目的是区分哪个地方的锁
 * @param int $time 加锁过期时间  防止 宕机后死锁
 * @param int $retry  加锁失败重试次数 就可能你前面的快出来了嘛 试几次就好了
 * @param int $sleep  重试的等待时间 微妙
 * @return bool 返回是否加锁成功啦
 */
public function lock($key = 'aaa',$time = 5,$retry = 5,$sleep =300000)
{
    $res = false;
    $key = 'lock'.$key;
    $value = session_create_id(md5(uniqid())); //首先这个呢我们要设个唯一值用来之后判断是谁的锁 避免误解锁的
    while ($retry-- > 0) {  //首先我们加锁的时候会循环重试
         //这个是加锁,NX代表 如果这个值存在就set失败 ex就是过期时间了
        $res = $this->redis->set($key,$value,['NX','EX'=>$time]);
        if ($res) { //如果加锁成功我们当然不需要重试啦 当然是直接跳出
            $this->value = $value;
            break;
        }
        echo "尝试再次获取锁\n";
        usleep($sleep); //这就是每次尝试的等待时间
    }
    //返回加锁的结果
    return $res;
}

/**
 * Notes: 这个是解锁的方法
 * Date: 2019/8/21
 * Time: 17:14
 * Author: zhangyizhao
 * @param string $key 解锁场景值 就是你要解哪吧锁
 */
public function offLock($key = 'aaa')
{
    //获取这把锁的值
    $value = $this->redis->get('lock'.$key);
    //判断一下这把锁是否是你的,万一你的锁过期了,解了别人的锁那别人蹲坑一半突然进来多个人怎办
    if ($value == $this->value) {
        //如果你的锁这时候就安心解吧
        $this->redis->del('lock'.$key);
    }
}

}
$redis = new Redis();
$redis->connect('127.0.0.1','6379');
//这块代码是模拟场景的
$lock = new Lock($redis);
//进来的用户先去拿吧锁
$res = $lock->lock();
//判断是否拿到了锁
if ($res) {

//只有拿到锁,才能执行你的业务逻辑
sleep(2); //这是模拟你处理了2秒的业务逻辑
echo  "很好很不错你拿到锁了";
$lock->offLock(); //处理完以后当然要把这把锁释放出去,这样别人才能执行嘛

} else {

//没拿到锁木得办法啦  只能重试啦
echo "执行失败";

}

这里总结一下

  1. set的NX主要是用于锁的互斥性,就只要一个人能拿到这把锁
  2. 设定锁的过期时间呢,主要是为了防止客户端执行到一半断了,没解锁造成死锁的问题
  3. 最好那个解锁判断value呢,主要是为了防止误解锁,你说万一你执行太长锁过期了,如果你不判断是不是自己的锁,去解了别人的锁,人家正蹲着坑呢,突然进来一个大汉,人家是不是心中万千草泥马在奔腾啊。

喝杯水

标签:PHP,Redis

最后编辑于:2019/08/21 19:55

添加新评论

  1. 2019-08-21 20:23

    22

    回复
  2. 老王 老王
    2019-08-21 19:59

    新文章啊

    回复

控制面板