php文件锁,缓存锁,解决微秒级并发控制

PHP实战   2025-08-14 11:39   131   0  

获取锁

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。

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。