开启掘金成长之旅!这是我参与「掘金日新计划 · 6 月更文挑战」的第 9 天,点击查看活动详情
实现分布式锁用到的Redis命令介绍:
setnx(key, value)
将key的值设为value,当且仅当key不存在。若给定的key已经存在,则SETNX不做任何动作。
SETNX是”SET if Not eXists”(如果不存在,则SET)的简写。返回值:设置成功,返回1。设置失败,返回0。
get(key)
返回key所关联的字符串值。如果key不存在则返回特殊值nil。假如key储存的值不是字符串类型,返回一个错误,因为GET只能用于处理字符串值。返回值:key的值。如果key不存在,返回nil。
getset(key, value)
将给定key的值设为value,并返回key的旧值。当key存在但不是字符串类型时,返回一个错误。返回值:返回给定key的旧值(old value)。当key没有旧值时,返回nil。
expire(key, seconds)
为给定key设置生存时间。当key过期时,它会被自动删除。
在Redis中,带有生存时间的key被称作“易失的”(volatile)。在低于2.1.3版本的Redis中,已存在的生存时间不可覆盖。
ttl(key)
返回给定key的剩余生存时间(time to live)(以秒为单位)。返回值:key的剩余生存时间(以秒为单位)。当key不存在或没有设置生存时间时,返回-1 。
del(key)
移除给定的一个或多个key。返回值:被移除key的数量。
在 PHP 中,可以使用 Redis 实现分布式锁。下面是一个示例代码,演示如何使用 Redis 实现基本的分布式锁:
<?php
$redis = new Redis();
$redis->connect('localhost', 6379);
// 锁的名称
$lockKey = 'my_lock';
// 获取锁
$lockAcquired = $redis->set($lockKey, 'locked', ['nx', 'ex' => 10]);
if ($lockAcquired) {
// 成功获取到锁
try {
// 在这里执行需要加锁的代码
// 模拟长时间运行的任务
sleep(5);
// 任务完成后释放锁
$redis->del($lockKey);
} catch (Exception $e) {
// 处理异常情况
$redis->del($lockKey);
// 抛出异常或进行其他处理
}
} else {
// 未能获取到锁,可以选择等待或直接返回错误
echo "Failed to acquire lock.";
}
在这个示例中,我们使用 Redis 的 SET 命令来设置一个键值对,其中键是锁的名称,值是表示锁定状态的标志(在此示例中,我们使用字符串 "locked")。通过将 nx 选项设置为 SET 命令,我们确保只有一个客户端能够成功设置该键,即获取到锁。
另外,我们还设置了 ex 选项,将锁的过期时间设置为 10 秒。这是为了防止死锁,以防锁的持有者在某种情况下未能释放锁。如果任务在规定的时间内未能完成,锁会自动过期,其他客户端可以获取到锁。
注意,这只是一个基本的示例,用于说明 Redis 分布式锁的实现原理。在实际生产环境中,还需要考虑更多的细节,如锁的超时处理、锁的可重入性等。另外,还可以使用 RedLock 算法等更复杂的方案来增强分布式锁的可靠性和性能。
具体实现的PHP完整代码:
<?php
/**
* 实现Redis分布锁
*/
$key = 'test'; //要更新信息的缓存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秒后再尝试执行操作
}
}
}