要在 PHP 中使用 Redis 实现分布式锁,可以使用类似的逻辑:通过 SET NX PX
命令获取锁,并通过唯一标识符(UUID)确保释放锁的正确性。以下是基于 PHP 的实现。
PHP 使用 Redis 实现分布式锁
1. 安装 Redis 扩展
在 PHP 中使用 Redis,你需要安装 phpredis
扩展。可以通过以下命令安装:
pecl install redis
安装完成后,确保在 php.ini
中启用了 Redis 扩展:
extension=redis.so
2. 实现分布式锁的 PHP 代码
<?php
class RedisLock {
private $redis;
private $lockKey;
private $lockTimeout;
private $identifier;
public function __construct($host = '127.0.0.1', $port = 6379) {
// 创建 Redis 连接
$this->redis = new Redis();
$this->redis->connect($host, $port);
}
/**
* 获取分布式锁
* @param string $lockKey 锁的键
* @param int $acquireTimeout 获取锁的超时时间,超过该时间则放弃获取锁
* @param int $lockTimeout 锁的过期时间(毫秒),防止死锁
* @return string|false 成功获取锁时返回锁的唯一标识,失败时返回 false
*/
public function acquireLock($lockKey, $acquireTimeout = 10000, $lockTimeout = 10000) {
$this->lockKey = $lockKey;
$this->lockTimeout = $lockTimeout;
$this->identifier = uniqid(); // 生成唯一标识符
$endTime = microtime(true) + $acquireTimeout / 1000;
// 尝试获取锁,直至超时
while (microtime(true) < $endTime) {
// SET 锁,如果成功(NX:键不存在,PX:过期时间为毫秒)
if ($this->redis->set($lockKey, $this->identifier, ['nx', 'px' => $lockTimeout])) {
return $this->identifier;
}
usleep(10000); // 睡眠 10 毫秒后重试
}
return false;
}
/**
* 释放分布式锁
* @return bool 是否成功释放锁
*/
public function releaseLock() {
// 使用 Lua 脚本保证原子性操作:只有锁的拥有者才能释放锁
$luaScript = '
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
';
return $this->redis->eval($luaScript, [$this->lockKey, $this->identifier], 1);
}
}
// 示例使用
$redisLock = new RedisLock();
$lockKey = 'my_distributed_lock';
$lockTimeout = 10000; // 锁的过期时间(10秒)
$lockIdentifier = $redisLock->acquireLock($lockKey, 5000, $lockTimeout); // 尝试 5 秒获取锁
if ($lockIdentifier) {
try {
// 获取锁成功,执行保护的业务逻辑
echo "Lock acquired, executing business logic...\n";
sleep(3); // 模拟业务处理
} finally {
// 释放锁
$redisLock->releaseLock();
echo "Lock released.\n";
}
} else {
echo "Failed to acquire lock.\n";
}
?>
3. PHP Redis 分布式锁的详细解释
-
连接 Redis:通过
new Redis()
创建 Redis 客户端,并使用connect()
方法连接到 Redis 服务。 -
获取锁:
SET key value NX PX timeout
是获取锁的核心命令:NX
表示仅当键不存在时才会设置键,确保只有一个客户端能获取锁。PX
表示设置锁的过期时间(以毫秒为单位),避免死锁。uniqid()
用来生成唯一标识符(identifier
),确保每个客户端获取锁时的标识唯一。
-
释放锁:使用 Lua 脚本来保证原子性操作,只有持有锁的客户端才可以释放锁。Lua 脚本的逻辑是:只有当 Redis 中存储的锁标识和当前客户端的标识相同时,才执行
DEL
操作删除锁。这样避免了因为竞争导致的锁被误删。
4. 关键点
- 死锁预防:通过设置锁的过期时间(
PX
参数)避免死锁,即使客户端崩溃,锁也会在指定时间后自动释放。 - 唯一标识符:每个客户端在获取锁时都会生成一个唯一的标识符,这个标识符用于确保释放的锁是当前客户端的锁,防止误删除他人的锁。
- Lua 脚本:Redis 中的 Lua 脚本可以保证操作的原子性,确保释放锁时检查和删除是一个不可分割的操作。
5. Redis 锁的局限性
虽然 Redis 锁方案简单高效,但它有一些局限性:
- 如果 Redis 集群有单点故障或者网络分区问题,可能会导致锁失效。
- 适用于对锁可靠性要求不是特别高的场景,如果需要更高的可靠性,可以考虑使用 Redlock 算法 提高容错性。