请教一下关于 Redis setnx 锁的使用是否正确?
V1 create 2023-12-20 09:00
想问一下下面这个获取锁的类,是否能在并发上限制,每次只会处理一个请求,或者下面这样用会有什么别的问题吗,感谢~
- Redis 锁
class RedisLock { const LOCK_NEGTIVE_BLOCK_TIME = 3 * 1000; // 阻塞3s (单位微秒) const LOCK_HOLD_TIME = 30 * 1000; // 持有锁的时间(单位微秒) const RETRY_DELAY = 100; // 循环尝试获取锁时的休眠时间(单位微秒) // Redis 锁 Key private string $lockKey; // Redis 值 Value (在此指代唯一请求ID) private string $requestId; public function __construct(string $lockKey, string $requestId) { $this->lockKey = $lockKey; $this->requestId = $requestId; } /** * 获取锁 * * @param int 锁的过期时间 * @param boolean 是否阻塞获取锁 * @param int $blockTime * @return bool */ public function get(int $expireTime = self::LOCK_HOLD_TIME, bool $isNegtive = true, int $blockTime = self::LOCK_NEGTIVE_BLOCK_TIME) { if ($isNegtive) { $endtime = microtime(true) * 1000 + $blockTime; // 尝试获取锁 while (microtime(true) * 1000 < $endtime) { if (Redis::set($this->lockKey, $this->requestId, 'PX', $expireTime, 'NX')) { return true; } // 如果获取失败,则会短暂休眠,多次重试 usleep(self::RETRY_DELAY); } return false; } else { // setnx 只获取一次锁 return (bool) Redis::set($this->lockKey, $this->requestId, 'PX', $expireTime, 'NX'); } } /** * 保证删除操作是原子性, (确保只有拥有锁的请求可以解锁) * * @return bool */ public function del() { // 检查 lockKey 的值是否等于 requestId,如果是,则删除该键,实现解锁 $luaScript = <<<EOF local key = KEYS[1] local value = ARGV[1] if redis.call("get", key) == value then return redis.call("del", key) else return 0 end EOF; return (bool) Redis::eval($luaScript, 1, $this->lockKey, $this->requestId); } } - 使用
// 获得锁 $redisLock = new RedisLock('MyLock', 'xxx'); $isok = $redisLock->get(); if(!$isok) { return false; } try { // do somthing } catch (\Throwable $th) { // handle exception } finally { // 释放锁 $redisLock->del(); } V2 update 2023-12-20 10:35
- 听了楼下的大佬分析后,进行调整,增加 run() 方法,结合闭包,锁住需要执行的逻辑
借鉴 Laravel 的使用方式 Cache::lock('foo', 10)->get(function () {});
/** * 在锁的范围内执行给定闭包。 * * @param closure $closure 需要执行的闭包 * @param int $expireTime 锁的过期时间(单位微秒) * @param bool $isNegtive 是否阻塞模式获取锁 * @param int $blockTime 阻塞时间(单位微秒) * * @return mixed 闭包的返回值,或者在未获取到锁时返回 false */ public function run(Closure $closure, int $expireTime = self::LOCK_HOLD_TIME, bool $isNegtive = true, int $blockTime = self::LOCK_NEGTIVE_BLOCK_TIME) { if ($this->get($expireTime, $isNegtive, $blockTime)) { try { // 在锁内执行闭包。 return $closure(); } catch (\Throwable $th) { // 处理异常,可以选择重新抛出或记录日志。 throw $th; } finally { // 无论如何都要释放锁。 $this->del(); } } return false; } // 伪静态 public static function lock(string $key, string $value, Closure $closure, int $expireTime = self::LOCK_HOLD_TIME, bool $isNegtive = true, int $blockTime = self::LOCK_NEGTIVE_BLOCK_TIME) { $lock = new self($key, $value); return $lock->run($closure, $expireTime, $isNegtive, $blockTime); } - 使用
// test 1 $result = (new RedisLock('requestLock', 'requestKey'))->run(function () { echo 'do something...'; // 如果需要,返回一些结果 return 'success'; }); // test 2 $result = RedisLock::lock('requestLock', 'requestKey', function () { echo 'do something...'; // 如果需要,返回一些结果 return 'success'; });
。
关于 LearnKu
你可以用
guzzlehttp/guzzle发送请求池试下锁用起来略显麻烦,可以抄 laravel的改下
laravel:
Cache::lock('foo', 10)->get(function () {});