aternos / lock
基于 etcd 的分布式独占和共享资源锁定
Requires
- php: >=8.0
- ext-json: *
- aternos/etcd: ^1.5.0
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-08-29 12:26:37 UTC
README
基于 etcd 的分布式独占和共享资源锁定(使用 aternos/etcd)。
关于
这个库是为了提供一个高度可用的分布式锁定解决方案而创建的,在文件存储系统中,失败的或错误的锁定可能导致潜在的数据丢失或损坏。因此,它试图在保持简单易用性、稳定性和可伸缩性的同时实现最佳的一致性。有许多其他的锁定库。其中一些不允许共享锁定,而另一些在客户端实现复杂的算法,这也不符合我们的需求。因此,我们决定创建自己的解决方案,让 etcd 承担大部分的重负载。
Etcd 是一个快速且可靠的键值存储,它允许事务操作以确保自上次读取以来没有发生意外的更改(参见 Aternos\Etcd\Client
中的 putIf()
和 deleteIf()
)。此锁定库使用这些方法来确保一致性。如果有两个进程同时尝试锁定相同的资源,etcd 会拒绝其中一个,然后它将重新尝试锁定,要么将其添加到另一个锁定(共享),要么等待另一个锁定完成(独占)。如果有更多进程争夺同一个锁定,它们将开始在随机间隔内延迟重试,以找到一致的结论。
etcd 的超时(不可用异常)也将被检测到,并在延迟后重试操作,以避免由于短暂不可用问题而引起的问题。
这个库是从不同的项目中提取出来的,用于不同的地方,而不仅仅是为了文件存储。因此,我们已经在生产中使用它,每分钟只需要几毫秒就能创建成千上万的锁定。
安装
必须安装 gRPC PHP 扩展才能使用此库。请参阅 aternos/etcd。
composer require aternos/lock
使用方法
最重要的类是 Lock
类。这个类的每个实例代表一个锁定在资源上,该资源由一个键标识。还有一些静态函数可以设置所有锁的选项,但它们都有默认值,所以您可以先创建一个锁
<?php $lock = new \Aternos\Lock\Lock("key"); // try to acquire lock if($lock->lock()) { // do something } else { // error/exit }
独占/共享锁定
同一时间只能有一个独占锁定,但可以有多个共享锁定。如果存在任何共享锁定,则不可能有独占锁定。独占锁定对于写操作非常有用,而共享锁定对于读操作非常有用。默认情况下,所有锁定都是共享的,您可以创建一个独占锁定如下所示
<?php $lock->lock(true);
锁定时间(超时)和刷新
您总是需要为您的锁设置超时时间。如果时间耗尽,锁将自动释放。这是为了避免无限死锁的必要措施。可以在写入或读取数据块后刷新超时时间。您还可以定义刷新时间的阈值。只有当剩余锁定时间低于此阈值时,锁才会被刷新。这样可以避免向etcd发送不必要的查询。
<?php $lock->lock(true, 60); // 60 seconds timeout // refresh the lock (with default values) $lock->refresh(); // refresh with 120 seconds timeout $lock->refresh(120); // refresh with 120 seconds timeout, but only if remaining time is lower than 60 seconds $lock->refresh(120, 60);
等待其他锁
如果您想要访问的资源当前被独占锁定,或者您需要独占锁而还有共享锁存在,您可能需要等待一段时间以等待锁被释放。您可以指定等待其他锁的最大时间。
<?php $lock->lock(true, 60, 300); // wait 300 seconds for other locks // check if the lock was actually acquired or if the wait timeout was reached if($lock->isLocked()) { // do something } else { // error/exit }
打破锁
当然,在操作完成后打破锁非常重要,以避免遇到超时。您可以像这样打破锁
<?php $lock = new \Aternos\Lock\Lock("key"); // check if the lock was successful if($lock->isLocked()) { // do something $lock->break(); } else { // error/exit }
标识符
您可以通过提供标识符字符串来识别自己(当前进程/请求)。默认情况下,这个库使用uniqid()
函数来创建一个唯一的标识符。很少有理由提供自定义标识符。默认标识符对于当前进程中的所有锁都是相同的。因此,所有这些锁都能够接管在同一进程中之前创建的其他锁。同时,也可以查看当前持有锁的其他进程(通过其标识符)是哪个。目前还没有这样的功能,但添加起来会很简单。
提供自定义标识符有两种方式。一种是设置所有锁的默认值,另一种是在特定锁上覆盖默认标识符。
<?php \Aternos\Lock\Lock::setDefaultIdentifier("default-identifier"); // uses the previously set "default-identifier" $lock->lock(); // overwrites the "default-identifier" with "different-identifier" $lock->lock(true, 60, 300, "different-identifier");
设置
上述默认标识符已经是静态设置之一,您可以为此指定所有锁。所有设置都存储在Lock
类(在顶部)的保护静态字段中,并且有它们自己的静态设置函数。下面仅列出了一些重要的设置,但您可以阅读PHPDoc函数注释以了解其他设置的进一步解释。只有当您知道自己在做什么时才更改这些设置。
Etcd客户端
您可以将etcd客户端对象设置为一个对象以指定etcd连接详情。有关更多信息,请参阅此处。
<?php $client = new Aternos\Etcd\Client(); $client = new Aternos\Etcd\Client("localhost:2379"); $client = new Aternos\Etcd\Client("localhost:2379", "username", "password"); \Aternos\Lock\Lock::setClient($client);
Etcd键前缀
您可以为etcd中所有锁定键设置前缀,以避免与其他数据在同一个etcd集群中的冲突。默认前缀是lock/
。
<?php \Aternos\Lock\Lock::setPrefix("my-prefix/");