要在 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. 关键点

  1. 死锁预防:通过设置锁的过期时间(PX 参数)避免死锁,即使客户端崩溃,锁也会在指定时间后自动释放。
  2. 唯一标识符:每个客户端在获取锁时都会生成一个唯一的标识符,这个标识符用于确保释放的锁是当前客户端的锁,防止误删除他人的锁。
  3. Lua 脚本:Redis 中的 Lua 脚本可以保证操作的原子性,确保释放锁时检查和删除是一个不可分割的操作。

5. Redis 锁的局限性

虽然 Redis 锁方案简单高效,但它有一些局限性:

  • 如果 Redis 集群有单点故障或者网络分区问题,可能会导致锁失效。
  • 适用于对锁可靠性要求不是特别高的场景,如果需要更高的可靠性,可以考虑使用 Redlock 算法 提高容错性。
Logo

更多推荐