fiberphp/lock

There is no license information available for the latest version (dev-master) of this package.

🔒 FiberPHP 分布式锁组件 —— 基于 Redis 的分布式锁实现,支持自动续期、阻塞等待、可重入,协程安全,开箱即用。

Maintainers

Package info

gitee.com/FiberPHP/lock

Issues

pkg:composer/fiberphp/lock

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

dev-master 2026-08-25 00:51 UTC

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_ttlacquire($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