要在 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] thenreturn redis.call("DEL", KEYS[1])elsereturn 0end';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 算法 提高容错性。