fiberphp / lock
🔒 FiberPHP 分布式锁组件 —— 基于 Redis 的分布式锁实现,支持自动续期、阻塞等待、可重入,协程安全,开箱即用。
Requires
- php: >=8.3
- fiberphp/framework: dev-master
- fiberphp/redis: dev-master
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-08-25 00:51:22 UTC
README
基于 Redis 的分布式锁组件,为常驻内存的 Workerman/FiberPHP 应用提供原子加锁、看门狗自动续期、安全释放(token 校验防误删)、阻塞获取与闭包式执行等能力。
特性
- 原子加锁:使用
SET key token NX EX ttl单命令原子获取,避免「检查 + 设置」竞争。 - 安全释放:释放锁通过 Lua 脚本比对 token,仅删除自己的锁,避免跨进程误删。
- 看门狗续期:基于
Workerman\Timer按配置间隔自动续期,业务执行超时不会导致锁提前过期被他人抢占。 - 多种获取方式:非阻塞
acquire、阻塞block(轮询等待)、闭包run(自动 try-finally 释放)。 - Redis 连接可配:支持使用任意
config/redis.php中定义的连接。 - 懒初始化:首次调用自动初始化,无需 ServiceProvider。
安装
composer require fiberphp/lock
安装完成后,包会自动:
- 将默认配置复制到
config/lock/app.php - 组件采用懒初始化,首次调用时自动读取配置并初始化,无需注册 ServiceProvider
配置
安装后配置位于 config/lock/app.php:
<?php
return [
'enable' => 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),否则续期逻辑不会启动,避免续期晚于过期导致的乱序。
使用
方式一:非阻塞获取(推荐高并发场景快速失败)
use FiberPHP\Lock\Locker;
$lock = Locker::acquire('order:' . $orderId);
if ($lock === null) {
throw new \RuntimeException('操作太频繁,请稍后重试');
}
try {
// 订单业务
} finally {
$lock->release();
}
自定义 TTL 和 token:
$lock = Locker::acquire('stock:sku_999', ttl: 15, token: 'my-unique-token');
方式二:阻塞获取(业务等待)
$lock = Locker::block('payment:order_123', ttl: 30, timeout: 5);
if ($lock === null) {
throw new \RuntimeException('系统繁忙,超过 5 秒仍未获取到锁');
}
try {
// 支付处理...
} finally {
$lock->release();
}
方式三:闭包式执行(最推荐)
自动完成「获取 → 执行 → 释放」,失败抛 LockException。
use FiberPHP\Lock\Locker;
$result = Locker::run('deduct_stock:' . $skuId, function () use ($skuId, $num) {
return OrderService::deductStock($skuId, $num);
}, ttl: 20, timeout: 3);
手动续期
$lock = Locker::acquire('longtask', ttl: 10);
// 一段长逻辑...
$lock->renew(); // 手动把锁续期到 10 秒(配置 TTL)
token 检查
$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」,保证过期后被其他进程拿到的锁,不会被旧持有者错误释放或续期。
-- 释放锁
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。
典型场景
| 场景 | 建议 |
|---|---|
| 秒杀扣库存 | Locker::acquire 非阻塞快速失败,返回"手慢了" |
| 下单幂等(按订单号) | Locker::run 闭包,简单可靠 |
| 定时任务单实例执行 | Locker::acquire('cron:xxx', 55, 'worker-' . posix_getpid()),TTL 略小于任务周期 |
| 资源同步(上传大文件/写大文件) | Locker::block,等待时间可放宽 |
目录结构
fiberphp/lock/
├── src/
│ ├── config/
│ │ └── lock.php # 默认配置
│ ├── Install.php # 子包安装器
│ ├── Lock.php # 单锁实例(token/释放/续期/看门狗)
│ ├── Locker.php # 静态门面(对外入口)
│ └── LockException.php # 锁异常
├── composer.json
└── README.md
依赖
| 依赖 | 约束 | 用途 |
|---|---|---|
php | >=8.2 | 运行时 |
fiberphp/framework | * | InstallHelper、config、异常基类、Redis 同步客户端(FiberPHP\Redis\Sync) |