aternos/lock

基于 etcd 的分布式独占和共享资源锁定

v1.2.0 2022-06-07 11:50 UTC

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/");