我的编程空间,编程开发者的网络收藏夹
学习永远不晚

PHP+Redis实现分布式锁

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

PHP+Redis实现分布式锁

目录

一、分布式锁概述

二、redis实现锁的命令

1、redis实现锁的命令

3、释放锁的步骤

三、PHP+redis分布式锁示例

四、redis集群分布式锁


一、分布式锁概述

        在分布式环境下,各个线程通过对公共资源的抢占,能够使一个代码块在同一时间只能被一个机器的一个线程执行,这个机制就是分布式锁。

        分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性

实现锁的操作主要有两个,即lock()和unlock()。

分布式锁实现的注意点:

1)互斥: 任意时刻, 只能有一个客户端获得锁

2)不会死锁: 客户端持有锁期间崩溃, 没有主动解除锁, 能保证后续的其他客户端获得锁

3)锁归属标识: 加锁和解锁的必须是同一个客户端, 客户端不能解掉非自己持有的锁(锁应具备标识)

4)如果是Redis集群, 还得考虑具有容错性: 只要大部分Redis节点正常运行, 客户端就可以加锁和解锁.

二、redis实现锁的命令

1、redis实现锁的命令

set key value NX EX

"NX" 仅在key不存在时加锁, 满足条件1: 互斥型

"EX" 设置锁过期时间(秒), 满足条件2: 避免死锁

上面这个set命令拆解开就是:

setnx cache_key random_value expire cache_key 30

虽然这两组命令执行的效果一样,但是第二个是非原子性操作,如果执行了setnx成功,但是expire失败的话,就会造成这个key一直存在了,无法释放的情况(死锁)。

使用随机数增加锁标识

public function lock(){    // 生成随机值    $this->lockValue = md5(uniqid());    return $this->redis->set($this->scene, $this->lockValue , ['NX', 'EX' => $this->expire]);}

使用锁标识(增加随机数)的原因:避免某个客户端获取锁后做其他操作过久而导致锁被自动释放,但该客户端以为还获得锁,这时假设其他客户端获取了锁,但上个客户端删除锁继而导致本客户端的锁被删除,总而言之客户端只能删除自己的锁,通过锁标识进行判断

3、释放锁的步骤

  • get 所持有锁
  • 判断这个锁是否自己所持有
  • 删除持有锁

所以,这三步要保证原子性,用lua脚本来执行,redis官方已经提供脚本文件。

if redis.call("get",KEYS[1]) == ARGV[1] then  return redis.call("del",KEYS[1])else  return 0end

        lua 脚本中执行 get 和 del 是原子性的, 整个lua脚本会被当做一条命令来执行,即使 get 后锁刚好过期, 此时也不会被其他客户端加锁,直到eval命令执行完成,Redis才会执行其他命令。如果不用原子性,还是会导致该锁被其他客户端获得,但上客户端已经判断过(自己所持有),进一步会删除该锁。

public function unLock(){    $script = <<redis->eval($script, [$this->scene, $this->lockValue], 1);}

eval方法的参数 3个,第一个是脚本代码(具体执行的代码),第二个是一个数组,参数数组,第三个参数是个整数,表示第key参数的数量,lua代码中的KEYS数量(不包括ARGV数量)

三、PHP+redis分布式锁示例

config = $config ? $config : ['host' => '127.0.0.1', 'port' => 6379];        $this->redis = $this->connect();    }    public function connect(){        $redis = new Redis();        $redis->connect($this->config['host'], $this->config['port']);        return $redis;    }        public function lock($scene = null , $expire = 10){        if (!$scene || !$expire){            return false;        }        // 生成随机值,锁标识        $lockId = md5(uniqid());        $result = $this->redis->set($scene, $lockId, ['NX', 'EX' => $expire]) ;        if($result)            return $lockId;        else            return $result;    }        public function unLock($scene, $lockId){        $lua = <<