mehr-it / lara-mysql-locks
基于 MySQL 5.7.5 及以上版本的分布式锁,用于 Laravel
Requires
- php: >=7.1
- ext-pdo: *
- doctrine/dbal: ^2.9|^3.0
- laravel/framework: ^5.8|^6.0|^7.0|^8.0|^9.0
Requires (Dev)
- ext-pcntl: *
- ext-posix: *
- orchestra/testbench: ^3.8|^4.0|^5.0|^6.0|^7.0
- phpunit/phpunit: ^7.4|^8.0|^9.5
README
此包实现了基于 MySQL 的分布式锁,具有以下功能:
- 无需预先初始化的命名日志
- 获取锁的等待超时
- 锁的 TTL(生存时间)
- 当锁过期并由其他进程接管时,数据库会话将被终止
- 如果未释放,进程结束或意外终止时,锁将自动释放
- 等待锁使用阻塞数据库请求,而不是轮询
- 在 TTL 之前释放的锁可以立即被其他进程获取
要求
- PHP >= 7.1
- MySQL >= 5.7.5(在 5.7.5 之前,每个连接只能获取一个命名锁)
此包仅适用于 MySQL 数据库连接!
安装
composer require mehr-it/lara-mysql-locks
此包使用 Laravel 的包自动发现,因此服务提供者和别名将被自动加载。
用法
$lock = DbLock::lock('my-lock', 5, 10);
// do some work
$lock->release();
lock()
方法期望锁名称、等待时间(在超时之前)和锁的最大生存时间(均为秒)。
如果无法在给定超时内获取锁,则会抛出 DbLockTimeoutException
。
release()
方法释放锁。如果数据库事务丢失或锁的 TTL 已过期且另一个进程获取了锁,则会失败。在这种情况下,将抛出 DbLockReleaseException
,表示锁已不再获取。
使用回调
应始终调用 release()
方法。因此,通常应使用 withLock()
方法获取锁并传递回调。 withLock()
确保即使抛出错误,也会释放锁
$return = DbLock::withLock(
function($lock) {
// do some work
}, 'my-lock', 5, 10
);
withLock()
方法在数据库事务中执行回调。 任何抛出或锁错误都会回滚事务。
您可以通过将连接名称传递给 withLock 来创建锁和事务,并使用不同的数据库连接
DbLock::withLock(function($lock) { }, 'my-lock', 5, 10, 'my-connection');
其他连接必须针对同一个 MySQL 实例,就像默认连接一样!否则,锁将立即释放!
锁和数据库连接
锁始终绑定到数据库连接。如果没有在创建锁时传递其他连接,则它们绑定到默认连接。
当锁的 TTL 过期并由另一个进程获取时,将终止绑定到锁的数据库连接!
在处理事务时必须考虑这一点。因为任何在事务中“锁丢失”之后的 SQL 查询都将失败。通常情况下(您通常不希望在锁 TTL 超过时提交任何内容),可能会出现不希望出现这种行为的情况。在这种情况下,您必须传递另一个数据库连接来创建锁
DbLock::lock('my-lock', 5, 10, 'other-connection');
此其他连接必须与默认连接针对同一个 MySQL 实例!否则,锁将立即释放!
如果不处理事务,Laravel 将优雅地重新连接到数据库,并且在这些情况下您的程序将按预期继续运行。
验证是否已获取锁
有时您可能需要检查是否已获取锁。这通常发生在执行影响其他资源(这些资源不受数据库事务覆盖)的操作时。
以下示例检查锁是否至少保持5秒钟。如果没有,将抛出DbLockRemainingTTLException
异常
$lock->assertAcquiredFor(5);
如果您不希望抛出异常,可以使用remainsAcquiredFor()
if (!$lock->remainsAcquiredFor(5)) {
// handle lock timeout
}
限制条件
MySQL对锁名称的长度有限制。因此,您不应使用超过50个字符的锁名称。