获取锁
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。