目标:什么是分布式锁,如何实现Redis分布式锁
什么是分布式锁
分布式锁是一种用于在分布式系统中实现资源的并发控制的机制。在分布式环境中,多个进程或节点需要协调对共享资源的访问,以避免并发访问导致的数据不一致或竞争条件的问题。分布式锁能够确保在多个进程或节点之间对共享资源的访问是有序的,避免了数据冲突和竞争条件。
分布式锁可以通过多个工具实现:基于数据库、基于缓存、基于ZooKeeper
- 锁的获取与释放:进程或节点在获取锁时需要确保只有一个能够成功获取到锁,其他需要等待或重试。在完成对共享资源的操作后,需要释放锁,以便其他进程或节点能够获取到锁。
- 锁的超时处理:为了避免因为某个进程或节点崩溃或异常导致锁一直被占用,可以设置锁的超时时间,在一定时间内未能正常释放锁时,可以由其他进程或节点获取锁。
- 锁的可重入性:有些场景下,同一个进程或节点可能需要多次获取同一个锁,因此需要考虑锁的可重入性,即同一个进程或节点能够多次获取同一个锁而不会造成死锁。
Redis分布式锁
简单的分布式锁
# Redis分布式锁
SET KEY VALUE EX [seconds] PX [milliseconds] NX XX
# EX seconds – 设置键key的过期时间,单位时秒
# PX milliseconds – 设置键key的过期时间,单位时毫秒
# NX – 只有键key不存在的时候才会设置key的值
# XX – 只有键key存在的时候才会设置key的值
释放锁时可能的bug
案例:
- 3个进程:A和B和C,在执行任务,并争抢锁,此时A获取了锁,并设置自动过期时间为60s
- A开始执行业务,因为某种原因,业务阻塞,耗时超过了60秒,此时锁自动释放了
- B恰好此时开始尝试获取锁,因为锁已经自动释放,成功获取锁
- A此时业务执行完毕,执行释放锁逻辑(删除key),于是B的锁被释放了,而B其实还在执行业务
- 此时进程C尝试获取锁,也成功了,因为A把B的锁删除了。
这种情况就需要可重入锁就是文章开头说的可重入性
PHP什时候需要用分布式锁
- 分布式系统:当你的应用程序部署在多个服务器上,每个服务器都有自己的PHP进程,并且这些进程都连接到同一个Redis服务器时,就存在多个应用程序实例同时访问Redis的可能性。
- 微服务架构:如果你的应用程序采用了微服务架构,其中不同的微服务可能运行在独立的服务器或容器中,并且每个微服务都可能需要访问共享的Redis服务,这样就会导致多个应用程序实例同时访问Redis。
- 负载均衡:当你使用负载均衡器将请求分发到多个应用程序实例时,每个实例都有自己的PHP进程,这些进程可能会同时访问Redis
PHP示例
// PHP分布式锁
// 创建一个Redis分布式锁类
class RedisDistributedLock
{
// Redis实例
private $redis;
// 锁的标识
private $lockKey;
// 可重入锁计数器
private $counter;
// 构造函数,初始化Redis实例和锁标识
public function __construct($redis, $lockKey)
{
$this--->redis = $redis;
$this->lockKey = $lockKey;
$this->counter = 0;
}
// 加锁方法
public function lock()
{
// 如果锁已经存在,增加计数器并返回true
if ($this->redis->exists($this->lockKey)) {
$this->counter++;
return true;
}
// 尝试获取锁
if ($this->redis->setnx($this->lockKey, 1)) {
// 设置锁过期时间,防止死锁
$this->redis->expire($this->lockKey, 10);
// 初始化计数器
$this->counter = 1;
return true;
}
// 获取锁失败
return false;
}
// 解锁方法
public function unlock()
{
// 如果计数器大于1,减少计数器并返回true
if ($this->counter > 1) {
$this->counter--;
return true;
}
// 删除锁
if ($this->redis->del($this->lockKey)) {
// 重置计数器
$this->counter = 0;
return true;
}
// 解锁失败
return false;
}
}
// 链接Reids
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
for ($i = 0; $i < 500; $i++) {
// 调用示例
$lockKey = 'Lock';
$lock = new RedisTool($redis, $lockKey);
$lock->lock();
// do something
$lock->unlock();
}
Comments NOTHING