# lock **Repository Path**: fiberphp/lock ## Basic Information - **Project Name**: lock - **Description**: 🔒 FiberPHP 分布式锁组件 —— 基于 Redis 的分布式锁实现,支持自动续期、阻塞等待、可重入,协程安全,开箱即用。 - **Primary Language**: PHP - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-23 - **Last Updated**: 2026-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fiberphp/lock 基于 Redis 的分布式锁组件,为常驻内存的 Workerman/FiberPHP 应用提供原子加锁、看门狗自动续期、安全释放(token 校验防误删)、阻塞获取与闭包式执行等能力。 ## 特性 - **原子加锁**:使用 `SET key token NX EX ttl` 单命令原子获取,避免「检查 + 设置」竞争。 - **安全释放**:释放锁通过 Lua 脚本比对 token,仅删除自己的锁,避免跨进程误删。 - **看门狗续期**:基于 `Workerman\Timer` 按配置间隔自动续期,业务执行超时不会导致锁提前过期被他人抢占。 - **多种获取方式**:非阻塞 `acquire`、阻塞 `block`(轮询等待)、闭包 `run`(自动 try-finally 释放)。 - **驱动可扩展**:内置 RedisLockDriver,可通过 `Locker::extend()` 注册自定义驱动(数据库锁、文件锁等)。 - **Redis 连接可配**:支持使用任意 `config/redis.php` 中定义的连接。 - **懒初始化**:首次调用自动初始化,无需 ServiceProvider。 ## 安装 ```bash composer require fiberphp/lock ``` 包内置 `config/lock.php` 默认配置由 config 包自动合并,装包即用;如需调整,在应用 `config/lock.php` 放置同名键覆盖默认值。 ## 配置 配置文件位于 `config/lock.php`,通过 `config('lock.xxx')` 访问: ```php true, // Redis 连接名,对应 config/redis.php 中的键 'connection' => 'default', // 锁 key 前缀 'prefix' => 'lock:', // 默认锁超时(秒),到期后自动释放,防死锁 'default_ttl' => 30, // 看门狗续期间隔(秒),0 = 不启用自动续期;必须 < default_ttl 'watchdog_interval' => 10, // 阻塞获取锁时的轮询间隔(毫秒) 'block_wait_ms' => 100, // 阻塞获取锁的最大等待时间(秒) 'block_timeout' => 5, ]; ``` > **关键提醒**:`watchdog_interval` 必须小于锁的 TTL(`default_ttl` 或 `acquire($ttl)` 传入的 `$ttl` >),否则续期逻辑不会启动,避免续期晚于过期导致的乱序。 ## 使用 ### 方式一:非阻塞获取(推荐高并发场景快速失败) ```php use FiberPHP\Lock\Locker; $lock = Locker::acquire('order:' . $orderId); if ($lock === null) { throw new \RuntimeException('操作太频繁,请稍后重试'); } try { // 订单业务 } finally { $lock->release(); } ``` 自定义 TTL 和 token: ```php $lock = Locker::acquire('stock:sku_999', ttl: 15, token: 'my-unique-token'); ``` ### 方式二:阻塞获取(业务等待) ```php $lock = Locker::block('payment:order_123', ttl: 30, timeout: 5); if ($lock === null) { throw new \RuntimeException('系统繁忙,超过 5 秒仍未获取到锁'); } try { // 支付处理... } finally { $lock->release(); } ``` ### 方式三:闭包式执行(最推荐) 自动完成「获取 → 执行 → 释放」,失败抛 `LockException`。 ```php use FiberPHP\Lock\Locker; $result = Locker::run('deduct_stock:' . $skuId, function () use ($skuId, $num) { return OrderService::deductStock($skuId, $num); }, ttl: 20, timeout: 3); ``` ### 手动续期 ```php $lock = Locker::acquire('longtask', ttl: 10); // 一段长逻辑... $lock->renew(); // 手动把锁续期到 10 秒(配置 TTL) ``` ### token 检查 ```php $lock = Locker::acquire('demo'); echo $lock->getKey(); // "lock:demo" echo $lock->getToken(); // 32 位随机十六进制 token ``` ## 核心设计 ### 防误删:token + Lua 每把锁 `SET` 的 value 是全局唯一的随机 token。`release()` / `renew()` 都通过 Lua 脚本原子执行「GET 比对 → DEL / EXPIRE」,保证过期后被其他进程拿到的锁,不会被旧持有者错误释放或续期。 ```lua -- 释放锁 if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0 ``` ### 看门狗自动续期 锁创建时,如果 `watchdog_interval > 0` 且 `< TTL`,会注册 `Workerman\Timer` 按间隔对锁调用 `EXPIRE`。若续期时发现 token 不再匹配(自己的锁已过期被替换),会自动清理定时器,避免无用轮询。 > ⚠️ 看门狗在 **Worker 进程**中有效。在 Worker 启动外使用(如 CLI 脚本),看门狗会因 `Timer::add()` 无效而静默不启动,此时业务必须靠自己估算好 > TTL。 ### 驱动可扩展 内置 `RedisLockDriver`(`SET NX EX` + Lua 校验)。自定义驱动实现 `LockDriverInterface` 后通过 `Locker::extend()` 注册: ```php use FiberPHP\Lock\Locker; Locker::extend('file', fn () => new FileLockDriver()); // config/lock.php: 'driver' => 'file' ``` ## 典型场景 | 场景 | 建议 | |---------------------------------|-----------------------------------------------------------------------------------| | 秒杀扣库存 | `Locker::acquire` 非阻塞快速失败,返回"手慢了" | | 下单幂等(按订单号) | `Locker::run` 闭包,简单可靠 | | 定时任务单实例执行 | `Locker::acquire('cron:xxx', 55, 'worker-' . posix_getpid())`,TTL 略小于任务周期 | | 资源同步(上传大文件/写大文件) | `Locker::block`,等待时间可放宽 | ## 目录结构 ``` src/ ├── Contract/ │ └── LockDriverInterface.php # 锁驱动契约 ├── Driver/ │ └── RedisLockDriver.php # Redis 锁驱动(SET NX EX + Lua) ├── Install.php # 安装钩子,定义配置路径 ├── Lock.php # 锁句柄(token/释放/续期/看门狗) ├── Locker.php # 静态门面(对外入口) ├── LockException.php # 锁异常 └── LockProvider.php # 服务提供者 ``` ## 依赖说明 | 依赖 | 说明 | |------------------------|---------------------------------------------------------------------------| | PHP >= 8.3 | 运行环境要求 | | `fiberphp/redis` | 提供 Redis 客户端,需配置名为 `default` 的连接(可配) | | `fiberphp/contract` | `ConfigRepository` / `ProviderInterface` 契约 | | `fiberphp/config` | 配置读取 | | `fiberphp/container` | `Locker::init()` 通过容器解析 `ConfigRepository` | | `fiberphp/discovery` | `PackageInstaller` 配置文件发布 | | `workerman/workerman` | (suggest)看门狗自动续期依赖 `Workerman\Timer`,纯 CLI 场景可不安 | 命名空间为 `FiberPHP\Lock\`(PSR-4,对应 `src/` 目录)。 ## License MIT License (c) 2026 庞斌,详见 [LICENSE](LICENSE)。