Redis 分布式锁

191 阅读1分钟

以下代码使用了 predis

简易版

$lock = $redis->setnx('lock', 'locked'); // 使用 setnx 加锁
        
if ($lock) {
    // ... 业务逻辑

    $redis->del(['lock']); // 解锁
}

使用 setnx 的原因是确保只会被一个客户端加锁, 意思是当不存在此 key 时才 set;

但这样写的话如果在处理业务逻辑代码时出现异常, 便会陷入死锁.

改进版 a: 设置过期时间以防死锁

$lock = $redis->setnx('lock', 'locked');

if ($lock) {
    $redis->expire('lock', 5); // 设置 5 秒后过期

    // ... 业务逻辑

    $redis->del(['lock']);
}

不过这种方式在极端情况下还是有问题, 如在加锁后到设置过期时间前这段时间服务器断网断电.

改进版 b: 同时 setnx, expire

$lock = $redis->set('lock', 'locked', 'EX', 5, 'NX'); // 第三个参数写 EX 表示秒, 写 PX 表示毫秒

if ($lock) {
    // ... 业务逻辑

    $redis->del(['lock']);
}

需要注意的是, 分布式锁不宜用于处理时间过长的业务, 以上面的方案为例, 假设一个业务处理完需要 6 秒, 而我 5 秒就过期了, 这时其他线程设置了锁, 一秒后刚刚过期的线程执行完毕删除了锁, 这就乱了.

改进版 c: 为锁打标签

$tag = rand(); // 设置一个随机数作为标签

$lock = $redis->set('lock', 'locked-' . $tag, 'EX', 5, 'NX');

if ($lock) {
    // ... 业务逻辑

    if ($redis->get('lock') == 'locked-' . $tag) { // 当值匹配时才解锁
        $redis->del(['lock']);
    }
}

其实这也不是最好的解决方案, 我还得深入学习一下再更新.