获取锁
LockService::acquire($lockKey)
释放锁
LockService::release($lockKey);
锁代码
<?php namespace app\common; use think\Cache; use think\Exception; use think\Log; /** * ANNOTATION:文件锁+缓存锁实现微妙级并发控制 * NAME:LockService */ class LockService { // 文件锁存储目录 const LOCK_DIR = '/tmp/app_www_cert_locks/'; // 默认锁超时时间(秒) const DEFAULT_LOCK_TIMEOUT = 30; // 锁类型常量 const LOCK_FILE = 1; const LOCK_MEMCACHED = 2; const LOCK_BOTH = 3; // 锁状态存储 private static $locks = []; /** * 尝试获取非阻塞锁 * * @param string $key 锁键名 * @param int $lockType 锁类型 (LOCK_FILE, LOCK_MEMCACHED, LOCK_BOTH) * @param float $timeout 超时时间(秒) * @return bool 是否成功获取锁 */ public static function acquire( string $key, int $lockType = self::LOCK_BOTH, float $timeout = self::DEFAULT_LOCK_TIMEOUT ): bool { $lockKey = self::normalizeKey($key); $startTime = microtime(true); // 检查锁类型是否有效 if (!in_array($lockType, [self::LOCK_FILE, self::LOCK_MEMCACHED, self::LOCK_BOTH])) { // Log::write("无效的锁定类型: {$lockType}",'ssl'); return false; } // 双重锁检查 $fileLockAcquired = false; $memcachedLockAcquired = false; // 文件锁获取 if ($lockType & self::LOCK_FILE) { $microtime = microtime(true); // Log::write("获取文件锁: {$key} |microtime:{$microtime}",'ssl'); $fileLockAcquired = self::acquireFileLock($lockKey, $timeout); // 文件锁失败立即返回 if (!$fileLockAcquired) { // Log::write("文件锁获取失败",'ssl'); return false; } $microtime = microtime(true); // Log::write("获取文件锁结束: {$key} |microtime:{$microtime}",'ssl'); } // Memcached锁获取 if ($lockType & self::LOCK_MEMCACHED) { $microtime = microtime(true); // Log::write("获取Memcached锁开始: {$key} |microtime:{$microtime}",'ssl'); $memcachedLockAcquired = self::acquireMemcachedLock($lockKey, $timeout); // Memcached锁失败则释放文件锁 if (!$memcachedLockAcquired && $fileLockAcquired) { Log::write("Memcached锁获取失败",'ssl'); self::releaseFileLock($lockKey); return false; } // Log::write("获取Memcached锁结束: {$key} |microtime:{$microtime}",'ssl'); } // 记录获取时间 $timeUsed = round((microtime(true) - $startTime) * 1000, 2); $microtime = microtime(true); // Log::write("锁定完成: {$key} | Type:{$lockType} | Time:{$timeUsed}ms | microtime:{$microtime}",'ssl'); // 存储锁状态 self::$locks[$lockKey] = [ 'type' => $lockType, 'file' => $fileLockAcquired, 'memcached' => $memcachedLockAcquired, 'acquire_time' => microtime(true) ]; // Log::write("锁数据:".json_encode(self::$locks[$lockKey],1),'ssl'); return true; } /** * 释放锁 * * @param string $key 锁键名 */ public static function release(string $key) { $lockKey = self::normalizeKey($key); if (!isset(self::$locks[$lockKey])) { Log::write("尝试释放未获取的锁: {$key}",'ssl'); return; } $lockInfo = self::$locks[$lockKey]; // 计算锁持有时间 $holdTime = round((microtime(true) - $lockInfo['acquire_time']) * 1000, 2); // 释放文件锁 if ($lockInfo['file']) { self::releaseFileLock($lockKey); } // 释放Memcached锁 if ($lockInfo['memcached']) { self::releaseMemcachedLock($lockKey); } // 清理状态 unset(self::$locks[$lockKey]); Log::write("锁已打开: {$key} | 等待时间: {$holdTime}ms",'ssl'); } /** * 获取文件锁(非阻塞) */ private static function acquireFileLock(string $lockKey, float $timeout): bool { // 确保锁目录存在 if (!is_dir(self::LOCK_DIR)) { if (!@mkdir(self::LOCK_DIR, 0777, true) && !is_dir(self::LOCK_DIR)) { Log::write("创建锁定目录失败: " . self::LOCK_DIR,'ssl'); return false; } } $lockFile = self::LOCK_DIR . $lockKey . '.lock'; // 打开文件(非阻塞模式) $fp = @fopen($lockFile, 'c+'); if (!$fp) { Log::write("无法打开锁文件: {$lockFile}",'ssl'); return false; } // 设置非阻塞模式 stream_set_blocking($fp, false); // 尝试获取非阻塞独占锁 $lockResult = flock($fp, LOCK_EX | LOCK_NB); if ($lockResult) { // 写入锁信息 ftruncate($fp, 0); fwrite($fp, sprintf( "PID:%d\nTime:%.6f\nKey:%s", getmypid(), microtime(true), $lockKey )); fflush($fp); // 保存文件指针 self::$locks[$lockKey]['fp'] = $fp; return true; } // 获取锁失败,关闭文件 fclose($fp); return false; } /** * 释放文件锁 */ private static function releaseFileLock(string $lockKey) { if (!isset(self::$locks[$lockKey]['fp'])) { return; } $fp = self::$locks[$lockKey]['fp']; // 释放锁 flock($fp, LOCK_UN); fclose($fp); // 清理文件 $lockFile = self::LOCK_DIR . $lockKey . '.lock'; if (file_exists($lockFile)) { @unlink($lockFile); } unset(self::$locks[$lockKey]['fp']); } /** * 获取Memcached锁(非阻塞) */ private static function acquireMemcachedLock(string $lockKey, float $timeout): bool { try { // 使用TP6的缓存驱动 $cache = Cache::store('memcached'); // 原子操作:仅当键不存在时设置 if (!Cache::has($lockKey)){ $cache->set($lockKey, microtime(true), $timeout); return true; } return false; } catch (\Throwable $e) { Log::write("Memcached 锁错误: " . $e->getMessage(),'ssl'); return false; } } /** * 释放Memcached锁 */ private static function releaseMemcachedLock(string $lockKey) { try { Cache::store('memcached')->rm($lockKey); } catch (\Throwable $e) { Log::write("Memcached 解锁错误: " . $e->getMessage(),'ssl'); } } /** * 标准化锁键名 */ private static function normalizeKey(string $key): string { // 过滤特殊字符,保留长度限制 return substr(preg_replace('/[^a-zA-Z0-9_\-]/', '_', $key), 0, 250); } /** * 获取当前锁状态(调试用) */ public static function status(): array { return self::$locks; } }
结语
文件锁能时间微妙级控制,但是缓存memached并不具备原子性,所以最好用redis。