cache_lock_thorws_excption = $cache_lock_thorws_excption; return $this; } public function getCacheLockThorwsExcption(){ return $this->cache_lock_thorws_excption; } public function hasRedis($is_interrupt = false) { $error_msg = ''; try { $redis = $this->getRedis(); // 检测连接是否正常 $redis->ping(); } catch (\BadFunctionCallException $e) { // 缺少扩展 $error_msg = $e->getMessage() ? $e->getMessage() : "缺少 redis 扩展"; } catch (\RedisException $e) { // 连接拒绝 \think\Log::write('redis connection redisException fail: ' . $e->getMessage()); $error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接失败"; } catch (\Exception $e) { // 异常 \think\Log::write('redis connection fail: ' . $e->getMessage()); $error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接异常"; } if ($error_msg) { if ($is_interrupt || $this->cache_lock_thorws_excption) { throw new \Exception($error_msg); } else { $this->setCacheLockErrorMsg($error_msg); return false; } } return true; } public function getRedis() { if (!isset($GLOBALS['SPREDIS'])) { $GLOBALS['SPREDIS'] = (new Redis())->getRedis(); } return $GLOBALS['SPREDIS']; } public function getLock($key, $suffix = "-lock-suffix", $timeout = 120, $lock_num = 1) { $this->hasRedis(true); $redis = $this->getRedis(); $hashKey = $key . $suffix; if ($lock_num == 1) { // 单令牌 $nxLock = $redis->set($hashKey, 1, ['nx', 'ex' => $timeout]); if ($nxLock == 1) { return true; } else { if ($this->cache_lock_thorws_excption) { throw new \Exception($this->cache_lock_error_msg); } else { return false; } } } else { // 多令牌 $lua = 'local hash_key = KEYS[1] local max_tokens = tonumber(ARGV[3]) local token_value = ARGV[1] local token_timeout = tonumber(ARGV[2]) -- 获取当前令牌数 local current_value = redis.call("hget", hash_key, "count") if not current_value then -- 锁不存在,初始化锁并设置过期时间 redis.call("hset", hash_key, "count", 1) redis.call("expire", hash_key, token_timeout) return 1 else -- 锁存在,增加计数器 local new_value = tonumber(current_value) + 1 if new_value > max_tokens then return 0 else redis.call("hset", hash_key, "count", new_value) redis.call("expire", hash_key, token_timeout) return new_value end end'; $result = $redis->eval($lua, [$hashKey, $timeout, $lock_num], 1); if ($result >= 1 && $result <= $lock_num) { return true; } else { if ($this->cache_lock_thorws_excption) { throw new \Exception($this->cache_lock_error_msg); } else { return false; } } } } public function setCacheLockErrorMsg($cache_lock_error_msg){ $this->cache_lock_error_msg = $cache_lock_error_msg; return $this; } public function getCacheLockErrorMsg(){ return $this->cache_lock_error_msg; } public function freeLock($key, $suffix = "-lock-suffix", $lock_num = 1, $timeout = 120) { $this->hasRedis(true); $redis = $this->getRedis(); if (!$key || !$suffix) return true; $hashKey = $key . $suffix; if ($lock_num == 1) { // 单令牌 // 执行lua脚本,确保删除锁是原子操作 $lua = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end'; $result = $redis->eval($lua, [$hashKey, 1], 1); if ('1' == $result) { return true; } return false; } else { // 多令牌 $lua = 'local hash_key = KEYS[1] local token_value = ARGV[1] local token_timeout = tonumber(ARGV[2]) -- 获取当前令牌数 local current_value = redis.call("hget", hash_key, "count") if not current_value then -- 锁不存在,无需释放 return 0 else -- 锁存在,减少计数器 local new_value = tonumber(current_value) - 1 if new_value < 1 then -- 计数器小于1,删除锁 local result = redis.call("del", hash_key) return result else -- 计数器大于等于1,更新锁 redis.call("hset", hash_key, "count", new_value) redis.call("expire", hash_key, token_timeout) return new_value end end'; $result = $redis->eval($lua, [$hashKey, $timeout, $lock_num], 1); if ($result >= 1 && $result <= $lock_num) { return true; } else { return false; } } } }