搜索

查看: 3122|回复: 11

[PHP] PHP使用redis实现分布式锁的示例详解

[复制链接]
发表于 2023-5-4 17:04:47 | 显示全部楼层 |阅读模式
Editor 2023-5-4 17:04:47 3122 11 看全部
目录
  • 什么是分布式锁
  • 实现原理
  • php实现代码最近在做一个领券功能的时候,发现在一定并发下会出现重复领券的问题。使用度娘一顿搜索操作之后,发现可以使用分布式锁来解决这个问题。

    什么是分布式锁
    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。

    实现原理
    实现分布式锁的原理很简单,就是需要有一把锁,多个服务同时去获取锁,但是只有一个服务能获取到锁。获取到锁的服务就可以执行自己的业务,没有获取到锁的其他服务需要等待获取到锁的服务业务执行完成后释放锁,然后再次尝试获取锁。
    实现分布式的方案有很多种。如下
  • 基于数据库实现分布式锁,比如mysql
  • 基于缓存实现分布式锁,比如redis
  • 基于Zookeeper实现分布式锁
    这里我们使用redis来实现分布式锁,在执行业务之前先获取一个key,如果key存在就说明已经有其他服务获得锁,这个时候需要等待或者返回系统繁忙。如果key不存在,说明没有其他服务获取锁,把这个key保存到redis,然后执行业务,等待业务执行完就从redis中删除这个key。

    php实现代码
    connect('127.0.0.1',6379);

            $this->redis = $redis;
        }
        public function getLock($key){
            $value = $this->redis->get($key);
            return $value;
        }

        public function setLock($key,$value){
            $this->redis->set($key,$value);
        }

        public function delLock($key){
            $lineNumber = $thid->redis->del($key);
            return $lineNumber;
        }
    }

    $key = 'your_lock_key';
    $value = time();

    $redisLock = new RedisLock();
    $isLock = $redisLock->get($key);
    if($isLock) {
        //已有锁,直接返回,不往下执行了
        return false;
    }

    //没有锁,加锁
    $redisLock->setLock($key,$value);



    //todo 执行业务逻辑
    sleep(5);

    // 解锁
    $redisLock->delLock($key);
    使用ab进行测试
    加锁     
    加锁     
    加锁     
    加锁     
    加锁     
    加锁     
    加锁     
    加锁     
    执行业务     
    解锁     
    解锁     
    执行业务     
    解锁     
    执行业务     
    解锁     
    执行业务     
    解锁     
    解锁     
    执行业务     
    解锁     
    加锁     
    执行业务     
    解锁     
    加锁     
    执行业务     
    解锁

    从测试结果来看,发现有多个执行业务,并没有完全锁住。这个是因为我们用的是redis的set命令。set 命令用于设置给定 key 的值。如果 key 已经存储其他值, SET 就覆写旧值,且无视类型。这样会导致很多服务都能加锁成功,而我们想要的是只有一个服务能加锁成功。
    要解决这个问题,需要了解redis的另一个命令setnx。setnx 命令在指定的 key 不存在时,为 key 设置指定的值。
    connect('127.0.0.1',6379);

            $this->redis = $redis;
        }
        public function getLock($key){
            $value = $this->redis->get($key);
            return $value;
        }

        public function setLock($key,$value){
            return $this->redis->setnx($key,$value);
        }

        public function delLock($key){
            $lineNumber = $thid->redis->del($key);
            return $lineNumber;
        }
    }

    $key = 'your_lock_key';
    $value = time();

    $redisLock = new RedisLock();
    $isLock = $redisLock->get($key);
    if($isLock) {
        //已有锁,直接返回,不往下执行了
        return false;
    }

    //没有锁,加锁
    $setLock = $redisLock->setLock($key,$value);
    if(!$setLock) {
        //加锁失败
        return false;
    }


    //todo 执行业务逻辑
    sleep(5);

    // 解锁
    $redisLock->delLock($key);
    再次使用ab进行测试
    加锁      
    加锁      
    加锁      
    加锁      
    加锁      
    加锁      
    加锁      
    加锁失败      
    加锁失败      
    加锁失败      
    加锁失败      
    加锁失败      
    已锁      
    已锁      
    已锁      
    执行业务      
    解锁

    从测试结果来看,在未加锁的状态下,有多个服务同时获取加锁,但是只有一个加锁成功, 其他的都是返回加锁失败,再后面的服务更是直接返回已锁。由此可见,加锁成功。
    那么到此就结束了吗?其实并不是的。假如在已加锁的情况执行业务,在业务过程中因为一些原因出现异常导致退出而没有进行解锁,那么将造成死锁,后面的所有服务都无法再次获取锁。为了解决这个问题,我们需要对锁设置一个过期的时间,防止死锁的发生。
    connect('127.0.0.1',6379);

            $this->redis = $redis;
        }
        public function getLock($key){
            $value = $this->redis->get($key);
            return $value;
        }

        public function setLock($key,$value,$second){
            $setnx = $this->redis->setnx($key,$value);
            if(!$setnx) {
                return $setnx;
            }
            $expire = $this->redis->expire($key,$second);
            if(!$expire) {
                $this->redis->del($key);
            }

            return $expire;
        }

        public function delLock($key){
            $lineNumber = $thid->redis->del($key);
            return $lineNumber;
        }
    }

    $key = 'your_lock_key';
    $value = time();

    $redisLock = new RedisLock();
    $isLock = $redisLock->get($key);
    if($isLock) {
        //已有锁,直接返回,不往下执行了
        return false;
    }

    //没有锁,加锁
    $second = 5;
    $setLock = $redisLock->setLock($key,$value,$second);
    if(!$setLock) {
        //加锁失败
        return false;
    }


    //todo 执行业务逻辑
    sleep(5);

    // 解锁
    $redisLock->delLock($key);
    以上就是PHP使用redis实现分布式锁的示例详解的详细内容,更多关于PHP redis分布式锁的资料请关注知鸟论坛其它相关文章!
  • 回复

    使用道具 举报

    发表于 2023-6-28 23:39:17 | 显示全部楼层
    我是的十八簿 2023-6-28 23:39:17 看全部
    楼主发贴辛苦了,谢谢楼主分享!我觉得知鸟论坛是注册对了!
    回复

    使用道具 举报

    发表于 2023-6-29 20:30:45 | 显示全部楼层
    掌舵的鱼1987 2023-6-29 20:30:45 看全部
    这个帖子不回对不起自己!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-30 00:42:49 | 显示全部楼层
    井底燕雀傥 2023-6-30 00:42:49 看全部
    楼主,我太崇拜你了!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-30 03:31:37 | 显示全部楼层
    小妖花满楼满fx 2023-6-30 03:31:37 看全部
    楼主,大恩不言谢了!知鸟论坛是最棒的!
    回复

    使用道具 举报

    发表于 2023-6-30 15:31:12 | 显示全部楼层
    丁侦球 2023-6-30 15:31:12 看全部
    楼主,我太崇拜你了!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-30 17:45:29 | 显示全部楼层
    落败的青春阳落s 2023-6-30 17:45:29 看全部
    其实我一直觉得楼主的品味不错!呵呵!知鸟论坛太棒了!
    回复

    使用道具 举报

    发表于 2023-6-30 20:55:32 | 显示全部楼层
    音乐之家1 2023-6-30 20:55:32 看全部
    既然你诚信诚意的推荐了,那我就勉为其难的看看吧!知鸟论坛不走平凡路。
    回复

    使用道具 举报

    发表于 2023-7-3 14:14:36 | 显示全部楼层
    六翼天使494 2023-7-3 14:14:36 看全部
    这东西我收了!谢谢楼主!知鸟论坛真好!
    回复

    使用道具 举报

    发表于 2023-7-3 21:13:29 | 显示全部楼层
    李志敏 2023-7-3 21:13:29 看全部
    论坛不能没有像楼主这样的人才啊!我会一直支持知鸟论坛
    回复

    使用道具 举报

    • 您可能感兴趣
    点击右侧快捷回复 【请勿灌水】
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则 返回列表

    RSS订阅| SiteMap| 小黑屋| 知鸟论坛
    联系邮箱E-mail:zniao@foxmail.com
    快速回复 返回顶部 返回列表