Skip to content

原子锁

DuxLite 提供了基于 Symfony Lock 组件的原子锁系统,支持多种存储后端,用于防止并发操作引起的数据竞争和重复执行问题。

🚀 快速开始

基础锁操作

php
use Core\App;

class OrderService
{
    public function processOrder(int $orderId): array
    {
        // 获取锁工厂
        $lockFactory = App::lock();
        
        // 创建锁
        $lock = $lockFactory->createLock("order_process:{$orderId}", 300); // 5分钟超时

        if (!$lock->acquire()) {
            return error("订单 {$orderId} 正在处理中,请勿重复操作");
        }

        try {
            // 执行需要锁保护的业务逻辑
            $this->doProcessOrder($orderId);
            
            return success('订单处理成功');
            
        } finally {
            // 确保释放锁
            $lock->release();
        }
    }

    private function doProcessOrder(int $orderId): void
    {
        // 具体的订单处理逻辑
        // 库存扣减、支付处理、物流安排等
    }
}

带超时的锁获取

php
// 创建带超时的锁(30秒自动过期)
$lock = $lockFactory->createLock('user_process', 30.0);

// 尝试获取锁,最多等待5秒
if ($lock->acquire(true, 5.0)) {
    try {
        // 处理用户相关操作
        $this->processUserData();
    } finally {
        $lock->release();
    }
} else {
    return error('获取锁超时,请稍后重试');
}

📋 锁配置

配置文件设置

config/use.toml 中配置锁类型:

toml
[lock]
# 锁类型:semaphore(信号量)、flock(文件锁)、redis(Redis锁)
type = "redis"

# 锁的默认超时时间(秒)
default_timeout = 60

存储类型对比

锁类型适用场景优点缺点
Semaphore单机多进程性能最佳,系统原生支持仅限单机,需要 sysvsem 扩展
Flock单机文件锁无需扩展,简单可靠仅限单机,依赖文件系统
Redis多进程/多机环境支持跨进程跨机器,功能丰富依赖 Redis 服务

Redis 锁配置

如果使用 Redis 锁,需要在 config/database.toml 中配置 Redis 连接:

toml
[redis.drivers.default]
host = "localhost"
port = 6379
password = ""
database = 3                 # 使用专门的锁数据库
timeout = 2.5

# 专用锁 Redis
[redis.drivers.lock]
host = "localhost"
port = 6379
password = ""
database = 4
timeout = 2.5
optPrefix = "lock_"

🔧 锁操作模式

阻塞模式

php
// 阻塞模式:等待获取锁,直到成功或超时
$lock = $lockFactory->createLock('resource', 30.0); // 30秒超时

if ($lock->acquire(true, 5.0)) { // 最多等待5秒
    try {
        // 执行业务逻辑
    } finally {
        $lock->release();
    }
} else {
    return error('获取锁超时');
}

非阻塞模式

php
// 非阻塞模式:立即返回结果
$lock = $lockFactory->createLock('resource');

if ($lock->acquire(false)) { // 不等待,立即返回
    try {
        // 执行业务逻辑
    } finally {
        $lock->release();
    }
} else {
    // 锁被占用,可以选择排队或者跳过
    $this->scheduleForLater();
}

获取不同类型的锁

php
// 获取默认锁工厂(从 use.toml 读取类型)
$lockFactory = App::lock();

// 获取指定类型的锁工厂
$redisLock = App::lock('redis');
$flockLock = App::lock('flock');
$semaphoreLock = App::lock('semaphore');

🎯 实际应用示例

1. 防止重复执行

php
class OrderService
{
    public function processOrder(int $orderId): array
    {
        $lockFactory = App::lock('redis');
        $lock = $lockFactory->createLock("order_process:{$orderId}", 300); // 5分钟超时

        if (!$lock->acquire()) {
            return error("订单 {$orderId} 正在处理中,请勿重复操作");
        }

        try {
            // 检查订单状态
            $order = Order::find($orderId);
            if ($order->status !== 'pending') {
                return success('订单已处理'); // 订单已处理
            }

            // 处理订单业务逻辑
            $this->doProcessOrder($order);

            // 更新订单状态
            $order->update(['status' => 'processing']);
            
            return success('订单处理成功');

        } finally {
            $lock->release();
        }
    }

    private function doProcessOrder(Order $order): void
    {
        // 具体的订单处理逻辑
        // 库存扣减、支付处理、物流安排等
    }
}

2. 文件上传去重

php
class FileUploadService
{
    public function uploadFile($file, string $userId): array
    {
        $fileHash = md5_file($file->getStream()->getMetadata('uri'));
        $lockFactory = App::lock('redis');
        $lock = $lockFactory->createLock("file_upload:{$fileHash}", 60);

        if (!$lock->acquire()) {
            return error('相同文件正在上传中,请稍后重试');
        }

        try {
            // 检查文件是否已存在
            $existingFile = File::where('hash', $fileHash)->first();
            if ($existingFile) {
                return success('文件已存在', ['file_id' => $existingFile->id]);
            }

            // 执行文件上传
            $uploadedFile = $this->doUploadFile($file, $userId, $fileHash);
            
            return success('文件上传成功', ['file_id' => $uploadedFile->id]);

        } finally {
            $lock->release();
        }
    }

    private function doUploadFile($file, string $userId, string $hash): File
    {
        // 实际的文件上传逻辑
        // 保存文件、生成缩略图、更新数据库等
    }
}

3. 定时任务防重复

php
class DataSyncService
{
    public function syncExternalData(): void
    {
        $lockFactory = App::lock('redis');
        $lock = $lockFactory->createLock('data_sync_task', 300); // 5分钟超时

        if (!$lock->acquire()) {
            logger()->info('数据同步任务已在运行中,跳过本次执行');
            return;
        }

        try {
            logger()->info('开始数据同步任务');

            // 获取上次同步时间
            $lastSync = $this->getLastSyncTime();

            // 从外部API获取增量数据
            $data = $this->fetchIncrementalData($lastSync);

            // 批量处理数据
            $this->processSyncData($data);

            // 更新同步时间戳
            $this->updateLastSyncTime();

            logger()->info('数据同步任务完成', [
                'processed_records' => count($data)
            ]);

        } catch (\Exception $e) {
            logger()->error('数据同步任务失败', [
                'error' => $e->getMessage()
            ]);
            throw $e;

        } finally {
            $lock->release();
        }
    }

    private function getLastSyncTime(): string
    {
        // 获取上次同步时间
    }

    private function fetchIncrementalData(string $lastSync): array
    {
        // 从外部API获取数据
    }

    private function processSyncData(array $data): void
    {
        // 处理同步数据
    }

    private function updateLastSyncTime(): void
    {
        // 更新同步时间
    }
}

4. 用户操作防并发

php
class UserService
{
    public function updateUserProfile(int $userId, array $data): array
    {
        $lockFactory = App::lock('redis');
        $lock = $lockFactory->createLock("user_update:{$userId}", 30);

        if (!$lock->acquire()) {
            return error('用户信息正在更新中,请稍后重试');
        }

        try {
            // 获取用户信息
            $user = User::find($userId);
            if (!$user) {
                return error('用户不存在');
            }

            // 验证数据
            $this->validateUserData($data);

            // 更新用户信息
            $user->update($data);

            // 清除相关缓存
            $this->clearUserCache($userId);

            return success('用户信息更新成功', $user->toArray());

        } finally {
            $lock->release();
        }
    }

    private function validateUserData(array $data): void
    {
        // 验证用户数据
    }

    private function clearUserCache(int $userId): void
    {
        // 清除用户相关缓存
    }
}

💡 最佳实践

1. 锁粒度控制

php
// ❌ 锁粒度过大
$lock = $lockFactory->createLock('all_users'); // 影响所有用户操作

// ✅ 合适的锁粒度
$lock = $lockFactory->createLock("user:{$userId}"); // 只影响特定用户

2. 超时时间优化

php
// ❌ 超时时间过长
$lock = $lockFactory->createLock('quick_task', 3600); // 1小时,过长

// ✅ 合理的超时时间
$lock = $lockFactory->createLock('quick_task', 30); // 30秒,合理

3. 非阻塞获取锁

php
// 非阻塞方式获取锁,快速失败
if ($lock->acquire(false)) {
    try {
        // 执行任务
    } finally {
        $lock->release();
    }
} else {
    // 快速返回或排队处理
    $this->queueTask($taskData);
}

4. 锁的安全释放

php
public function safeExecuteWithLock(string $resource, callable $callback): mixed
{
    $lockFactory = App::lock();
    $lock = $lockFactory->createLock($resource, 60);

    try {
        if (!$lock->acquire(true, 5.0)) {
            throw new \Exception("获取锁失败: {$resource}");
        }

        return $callback();

    } catch (\Exception $e) {
        logger()->error('锁保护的操作失败', [
            'resource' => $resource,
            'error' => $e->getMessage()
        ]);
        throw $e;

    } finally {
        try {
            $lock->release();
        } catch (\Exception $e) {
            logger()->warning('释放锁失败', [
                'resource' => $resource,
                'error' => $e->getMessage()
            ]);
        }
    }
}

🎉 总结

DuxLite 原子锁系统的特点:

  • 🔒 多种存储:支持 Semaphore、Flock、Redis 三种锁类型
  • ⚡ 高性能:基于 Symfony Lock 组件,性能优异
  • 🛡️ 防并发:有效防止数据竞争和重复执行
  • 🔧 易配置:简单的配置文件设置
  • 📊 灵活控制:支持阻塞和非阻塞模式

合理使用原子锁可以有效解决高并发场景下的数据一致性问题!