以下代码使用了 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']);
}
}
其实这也不是最好的解决方案, 我还得深入学习一下再更新.