Categories
PHP

PHP 通过 Redis NX 特性实现独占锁

对于简单的业务,我们可以通过单实例的 Redis 实现一个全局独占锁。笔者以 PHP 为例,实现一个加锁类。

实现原理

对于 Redis 的 SET 命令,支持 NX 特性,官方解释为:Only set the key if it does not already exist。也就是,当 key 不存在时设置成功后,才会返回 OK,否则返回 nil。这样,当多个并发请求设置 key 时,只有一个会成功。

然后,结合 EX 过期时长,指定加锁时长,即过期后自动释放锁。再通过指定 key 的值为随机 Token,来保证当前操作不会错误的释放其它业务请求的锁。详见下文实例。

SET 用法如下:

set {LOCK_NAME} {LOCK_TOKEN} EX {LEASE_TIME} NX

具体实现

实例的完整源码,放到了:https://github.com/xingchaovv/php-example/tree/master/src/RedisLocker,请参考使用。源码基于 PHP 8。

方法 lock 实现

/**
 * 加锁
 * @return bool 成功时返回 true,失败时返回 false
 */
public function lock(): bool {
    $lockName = self::LOCK_NAME_PREFIX . $this->lockName;
    $lockToken = uniqid('', true);
    // set {LOCK_NAME} {LOCK_TOKEN} EX {LEASE_TIME} NX
    $isLocked = self::redis()->rawCommand('set', $lockName, $lockToken, 'EX', $this->leaseTime, 'NX');
    if (!$isLocked) {
        return false;
    }
    $this->lockToken = $lockToken;
    return true;
}

方法 unlock 实现

/**
 * 解锁
 * @return bool 成功时返回 true,失败时返回 false
 */
public function unlock(): bool {
    if (!$this->lockToken) {
        return false;
    }
    $lockName = self::LOCK_NAME_PREFIX . $this->lockName;
    if (self::redis()->get($lockName) !== $this->lockToken) {
        return false;
    }
    self::redis()->del($lockName);
    $this->lockToken = '';
    return true;
}

用法

// 针对 order101 构建锁实例
$locker = RedisLocker::valueOf("order101");
// 加锁
$lockResult = $locker->lock();
printf("[%s] order101 lock result: %s\n", date('r'), print_r($lockResult, true));
printf("[%s] order101: %s\n", date('r'), print_r($locker, true));
// 解锁
$unlockResult = $locker->unlock();
printf("[%s] order101 unlock result: %s\n", date('r'), print_r($unlockResult, true));
printf("[%s] order101: %s\n", date('r'), print_r($locker, true));

(完)

扫码阅读和分享

Leave a Reply

Your email address will not be published. Required fields are marked *