Home
Linux
Golang
MySQL
PHP
Other
Redis锁
创建日期:2020-12-15 19:48:12
更新日期:2023-06-06 04:33:33
栏目:
PHP
浏览:1334
[TOC] ## 早期方案:setnx `SETNX` :只有在指定 key 不存在时才为 key 设置相应的值,[命令详解](https://redis.io/commands/setnx "命令详解") 先介绍常用的一个误区 - 一、C1 和 C2 去检查锁状态,都返回 false,此时锁是由 C3 持有,C3 在持有锁之后崩溃了 - 二、C1 发送 del,然后发送 setnx。成功 - 三、C2 发送 del,然后发送 setnx。成功 - 四、这样 C1 和 C2 都获得了锁,这样就会导致出现错误 以下为正确的做法: - 1、C4 去检查锁状态,返回 false,此时锁是由 C3 持有,C3 在持有锁之后崩溃了 - 2、C4 发送 get 请求,检查锁是否过期 - 2.1、如果未过期,将休眠一段时间并从第一步开始重试 - 2.2、如果已过期,则执行 `GETSET lock.foo <current Unix timestamp + lock timeout + 1>` - 2.3、检查 GETSET 返回值中的时间戳是否是已过期的时间戳,如果是,则表示获得了锁 - 2.4、如果 GETSET 返回值中的时间戳不是已过期的时间戳,就证明有人比 C4 快,这个时候 C4 去重新走第一步 代码示例: ``` private function redisLock(string $key, int $expire = 5) { $time = time(); $is_lock = Redis::setnx($key, $time + $expire); // 此处加过期时间,是为了避免大量未过期的键 Redis::expire($key, 3600); // 不能获取锁 if (!$is_lock) { // 判断锁是否过期 $lock_time = Redis::get($key); // 锁已过期,删除锁,重新获取 if ($time > $lock_time) { $this->redisUnlock($key); $is_lock = Redis::setnx($key, $time + $expire); Redis::expire($key, 3600); } } return $is_lock ? true : false; } /** * 释放锁 * 不能随意释放锁,会导致将其他人的锁删掉,删除锁的动作只能由上一步获取锁的地方发起 * 可以直接使用说明中的 getset 命令来操作, * * @param string $key 锁标识 * @return Boolean */ private function redisUnlock(string $key) { return Redis::del($key); } ``` ## 官方建议:set ``` /** * 直接采用 set 方式来加锁获取锁 * 官方文档:https://redis.io/commands/set * EX 设置指定的到期时间,秒 * PX 设置指定的到期时间,毫秒 * NX 当键不存在时设置的 * XX 当键已存在时设置 * KEEPTTL 保留相关的过期时间 * * @param string $key 锁标识 * @param string $value 锁内容,设置一个不重复,不可预测的随机字符串。可以使系统更加强大 * @param int $expire 过期时间 * @return mixed */ private function redisLockNew($key, $value, $expire = 30) { // 下面这几种写法无效哦, // return Redis::set($key, $value, "NX", "EX", $expire); // 这样是永久过期 // return Redis::set($key, $value, ["NX", "EX" => $expire]); // 这个直接报错 // 用脚本写 $script = <<<SCRIPT return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) SCRIPT; return Redis::eval($script, 1, $key, $value, $expire); } /** * 删除锁,采用 lua 脚本来保证原子性 * @param $key * @param $value * @return mixed */ private function redisUnlockNew($key, $value) { $script = ` if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end`; return Redis::eval($script, 1, $key, $value); } ``` ## 附录 - 参考地址:[Redis官方解决方案](https://redis.io/topics/distlock "Redis官方解决方案")
内容版权声明:本文为舒孝元原创文章,转载无需和我联系,但请注明来自
舒孝元博客:https://www.shuxiaoyuan.com/info/89
联系邮箱:sxy@shuxiaoyuan.com