锁组件
锁组件创建和管理 锁,这是一种为共享资源提供独占访问的机制。
如果您正在使用 Symfony 框架,请阅读 Symfony 框架锁文档。
安装
1
$ composer require symfony/lock
注意
如果您在 Symfony 应用程序之外安装此组件,则必须在代码中引入 vendor/autoload.php
文件,以启用 Composer 提供的类自动加载机制。阅读 这篇文章 了解更多详情。
用法
锁用于保证对某些共享资源的独占访问。在 Symfony 应用程序中,例如,您可以使用锁来确保命令在同一时间(在同一台或不同的服务器上)不会被执行多次。
锁是使用 LockFactory 类创建的,而 LockFactory 类又需要另一个类来管理锁的存储
1 2 3 4 5
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\SemaphoreStore;
$store = new SemaphoreStore();
$factory = new LockFactory($store);
锁是通过调用 createLock() 方法创建的。它的第一个参数是表示锁定资源的任意字符串。然后,调用 acquire() 方法将尝试获取锁
1 2 3 4 5 6 7 8 9
// ...
$lock = $factory->createLock('pdf-creation');
if ($lock->acquire()) {
// The resource "pdf-creation" is locked.
// You can compute and generate the invoice safely here.
$lock->release();
}
如果无法获取锁,该方法将返回 false
。即使锁已被获取,也可以安全地重复调用 acquire()
方法。
注意
与其他实现不同,即使为同一资源创建锁实例,Lock 组件也会区分它们。这意味着对于给定的范围和资源,一个锁实例可以被多次获取。如果锁必须被多个服务使用,它们应该共享 LockFactory::createLock
方法返回的同一个 Lock
实例。
提示
如果您不显式释放锁,它将在实例销毁时自动释放。在某些情况下,跨多个请求锁定资源可能很有用。要禁用自动释放行为,请将 createLock()
方法的第三个参数设置为 false
。
序列化锁
Key 包含 Lock 的状态并且可以被序列化。这允许用户在一个进程中通过获取锁开始一个长时间的任务,并在另一个进程中使用同一个锁继续该任务。
首先,您可以创建一个可序列化的类,其中包含资源和锁的键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// src/Lock/RefreshTaxonomy.php
namespace App\Lock;
use Symfony\Component\Lock\Key;
class RefreshTaxonomy
{
public function __construct(
private object $article,
private Key $key,
) {
}
public function getArticle(): object
{
return $this->article;
}
public function getKey(): Key
{
return $this->key;
}
}
然后,您可以使用此类来分发另一个进程处理剩余作业所需的一切
1 2 3 4 5 6 7 8 9 10 11 12
use App\Lock\RefreshTaxonomy;
use Symfony\Component\Lock\Key;
$key = new Key('article.'.$article->getId());
$lock = $factory->createLockFromKey(
$key,
300, // ttl
false // autoRelease
);
$lock->acquire(true);
$this->bus->dispatch(new RefreshTaxonomy($article, $key));
注意
不要忘记在 Lock
实例化中将 autoRelease
参数设置为 false
,以避免在析构函数被调用时释放锁。
并非所有存储都与序列化和跨进程锁定兼容:例如,内核将自动释放 SemaphoreStore 存储获取的信号量。如果您使用不兼容的存储(请参阅 锁存储 以了解支持的存储),当应用程序尝试序列化键时,将抛出异常。
阻塞锁
默认情况下,当无法获取锁时,acquire
方法会立即返回 false
。要等待(无限期地)直到可以创建锁,请将 true
作为 acquire()
方法的参数传递。这被称为阻塞锁,因为您的应用程序的执行会停止,直到获取到锁
1 2 3 4 5 6 7 8
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
$store = new FlockStore('/var/stores');
$factory = new LockFactory($store);
$lock = $factory->createLock('pdf-creation');
$lock->acquire(true);
当存储不支持通过实现 BlockingStoreInterface 接口来阻塞锁时(请参阅 锁存储 以了解支持的存储),Lock
类将以非阻塞方式重试获取锁,直到获取到锁。
过期锁
远程创建的锁很难管理,因为远程 Store
无法知道锁定进程是否仍然存活。由于错误、致命错误或段错误,无法保证 release()
方法会被调用,这将导致资源被无限期锁定。
在这些情况下,最好的解决方案是创建过期锁,这些锁在经过一段时间后(称为 TTL,即 Time To Live)会自动释放。此时间(以秒为单位)配置为 createLock()
方法的第二个参数。如果需要,这些锁也可以使用 release()
方法提前释放。
使用过期锁时最棘手的部分是选择合适的 TTL。如果 TTL 太短,其他进程可能会在作业完成之前获取锁;如果 TTL 太长,并且进程在调用 release()
方法之前崩溃,则资源将保持锁定状态,直到超时。
1 2 3 4 5 6 7 8 9 10 11 12
// ...
// create an expiring lock that lasts 30 seconds (default is 300.0)
$lock = $factory->createLock('pdf-creation', ttl: 30);
if (!$lock->acquire()) {
return;
}
try {
// perform a job during less than 30 seconds
} finally {
$lock->release();
}
提示
为了避免将锁置于锁定状态,建议将作业包装在 try/catch/finally 块中,以始终尝试释放过期锁。
对于长时间运行的任务,最好从不太长的 TTL 开始,然后使用 refresh() 方法将 TTL 重置为其原始值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// ...
$lock = $factory->createLock('pdf-creation', ttl: 30);
if (!$lock->acquire()) {
return;
}
try {
while (!$finished) {
// perform a small part of the job.
// renew the lock for 30 more seconds.
$lock->refresh();
}
} finally {
$lock->release();
}
提示
对于长时间运行的任务,另一个有用的技术是将自定义 TTL 作为 refresh()
方法的参数传递,以更改默认锁 TTL
1 2 3 4 5 6 7
$lock = $factory->createLock('pdf-creation', ttl: 30);
// ...
// refresh the lock for 30 seconds
$lock->refresh();
// ...
// refresh the lock for 600 seconds (next refresh() call will be 30 seconds again)
$lock->refresh(600);
此组件还提供了两个与过期锁相关的有用方法:getRemainingLifetime()
(返回 null
或一个浮点数,以秒为单位)和 isExpired()
(返回布尔值)。
自动释放锁
当 Lock 对象被销毁时,锁会自动释放。这是一个实现细节,在进程之间共享锁时非常重要。在下面的示例中,pcntl_fork()
创建了两个进程,并且一旦一个进程完成,锁将自动释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// ...
$lock = $factory->createLock('pdf-creation');
if (!$lock->acquire()) {
return;
}
$pid = pcntl_fork();
if (-1 === $pid) {
// Could not fork
exit(1);
} elseif ($pid) {
// Parent process
sleep(30);
} else {
// Child process
echo 'The lock will be released now.';
exit(0);
}
// ...
注意
为了使上面的示例能够工作,必须安装 PCNTL 扩展。
要禁用此行为,请将 LockFactory::createLock()
的 autoRelease
参数设置为 false
。这将使锁被获取 3600 秒或直到 Lock::release()
被调用
1 2 3 4 5
$lock = $factory->createLock(
'pdf-creation',
3600, // ttl
false // autoRelease
);
共享锁
共享锁或读者-写者锁是一种同步原语,它允许并发访问只读操作,而写操作需要独占访问。这意味着多个线程可以并行读取数据,但写入或修改数据需要独占锁。它们例如用于无法原子更新的数据结构,并且在更新完成之前无效。
使用 acquireRead() 方法获取只读锁,使用 acquire() 方法获取写锁
1 2 3 4
$lock = $factory->createLock('user-'.$user->id);
if (!$lock->acquireRead()) {
return;
}
与 acquire()
方法类似,将 true
作为 acquireRead()
的参数传递以在阻塞模式下获取锁
1 2
$lock = $factory->createLock('user-'.$user->id);
$lock->acquireRead(true);
注意
Symfony 共享锁的 优先级策略取决于底层存储(例如,Redis 存储优先考虑读者而不是写者)。
当使用 acquireRead()
方法获取只读锁时,可以通过调用 acquire()
方法来提升锁,并将其更改为写锁
1 2 3 4 5 6 7 8 9
$lock = $factory->createLock('user-'.$userId);
$lock->acquireRead(true);
if (!$this->shouldUpdate($userId)) {
return;
}
$lock->acquire(true); // Promote the lock to a write lock
$this->update($userId);
同样,也可以通过调用 acquireRead()
方法来降级写锁,并将其更改为只读锁。
当提供的存储未实现 SharedLockStoreInterface 接口时(请参阅 锁存储 以了解支持的存储),Lock
类将通过调用 acquire()
方法回退到写锁。
锁的所有者
首次获取的锁由获取它的 Lock
实例 所有。如果您需要检查当前的 Lock
实例是否(仍然)是锁的所有者,您可以使用 isAcquired()
方法
1 2 3
if ($lock->isAcquired()) {
// We (still) own the lock
}
由于某些锁存储具有过期锁,因此实例可能会自动丢失其获取的锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// If we cannot acquire ourselves, it means some other process is already working on it
if (!$lock->acquire()) {
return;
}
$this->beginTransaction();
// Perform a very long process that might exceed TTL of the lock
if ($lock->isAcquired()) {
// Still all good, no other instance has acquired the lock in the meantime, we're safe
$this->commit();
} else {
// Bummer! Our lock has apparently exceeded TTL and another process has started in
// the meantime so it's not safe for us to commit.
$this->rollback();
throw new \Exception('Process failed');
}
警告
一个常见的误区可能是使用 isAcquired()
方法来检查锁是否已被任何进程获取。正如您在此示例中看到的,您必须为此使用 acquire()
。isAcquired()
方法仅用于检查锁是否已被当前进程获取。
注意
从技术上讲,锁的真正所有者是那些共享同一个 Key
实例的人,而不是 Lock
实例。但从用户的角度来看,Key
是内部的,您可能只会使用 Lock
实例,因此将 Lock
实例视为锁的所有者更容易理解。
可用存储
锁是在 存储
中创建和管理的,存储是实现 PersistingStoreInterface 以及可选的 BlockingStoreInterface 的类。
该组件包含以下内置存储类型
存储 | 作用域 | 阻塞 | 过期 | 共享 | 序列化 |
---|---|---|---|---|---|
FlockStore | 本地 | 是 | 否 | 是 | 否 |
MemcachedStore | 远程 | 否 | 是 | 否 | 是 |
MongoDbStore | 远程 | 否 | 是 | 否 | 是 |
PdoStore | 远程 | 否 | 是 | 否 | 是 |
DoctrineDbalStore | 远程 | 否 | 是 | 否 | 是 |
PostgreSqlStore | 远程 | 是 | 否 | 是 | 否 |
DoctrineDbalPostgreSqlStore | 远程 | 是 | 否 | 是 | 否 |
RedisStore | 远程 | 否 | 是 | 是 | 是 |
SemaphoreStore | 本地 | 是 | 否 | 否 | 否 |
ZookeeperStore | 远程 | 否 | 否 | 否 | 否 |
提示
Symfony 包含了另外两个特殊的存储,它们主要用于测试:InMemoryStore
,它在进程期间将锁保存在内存中;以及 NullStore
,它不持久化任何内容。
7.2
NullStore 在 Symfony 7.2 中引入。
FlockStore
FlockStore 使用本地计算机上的文件系统来创建锁。它不支持过期,但当锁对象超出作用域并被垃圾回收器释放时(例如,当 PHP 进程结束时),锁会自动释放
1 2 3 4 5
use Symfony\Component\Lock\Store\FlockStore;
// the argument is the path of the directory where the locks are created
// if none is given, sys_get_temp_dir() is used internally.
$store = new FlockStore('/var/stores');
警告
请注意,某些文件系统(例如某些类型的 NFS)不支持锁定。在这些情况下,最好使用本地磁盘驱动器上的目录或远程存储。
MemcachedStore
MemcachedStore 将锁保存在 Memcached 服务器上,它需要实现 \Memcached
类的 Memcached 连接。此存储不支持阻塞,并期望使用 TTL 以避免死锁
1 2 3 4 5 6
use Symfony\Component\Lock\Store\MemcachedStore;
$memcached = new \Memcached();
$memcached->addServer('localhost', 11211);
$store = new MemcachedStore($memcached);
注意
Memcached 不支持低于 1 秒的 TTL。
MongoDbStore
MongoDbStore 将锁保存在 MongoDB 服务器 >=2.2 上,它需要来自 mongodb/mongodb 的 \MongoDB\Collection
或 \MongoDB\Client
,或者 MongoDB 连接字符串。此存储不支持阻塞,并期望使用 TTL 以避免死锁
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\Lock\Store\MongoDbStore;
$mongo = 'mongodb://127.0.0.1/database?collection=lock';
$options = [
'gcProbability' => 0.001,
'database' => 'myapp',
'collection' => 'lock',
'uriOptions' => [],
'driverOptions' => [],
];
$store = new MongoDbStore($mongo, $options);
MongoDbStore
接受以下 $options
(取决于第一个参数类型)
选项 | 描述 |
---|---|
gcProbability | 是否应创建 TTL 索引,表示为从 0.0 到 1.0 的概率(默认为 0.001 ) |
数据库 | 数据库的名称 |
集合 | 集合的名称 |
uriOptions | MongoDBClient::__construct 的 URI 选项数组 |
driverOptions | MongoDBClient::__construct 的驱动程序选项数组 |
当第一个参数是
MongoDB\Collection
:
$options['database']
被忽略$options['collection']
被忽略
MongoDB\Client
:
$options['database']
是必需的$options['collection']
是必需的
MongoDB 连接字符串
- 使用
$options['database']
,否则使用来自 DSN 的/path
,至少需要一个 - 使用
$options['collection']
,否则使用来自 DSN 的?collection=
,至少需要一个
注意
collection
查询字符串参数不是 MongoDB 连接字符串 定义的一部分。它用于允许使用 数据源名称 (DSN) 构建 MongoDbStore
,而无需 $options
。
PdoStore
PdoStore 将锁保存在 SQL 数据库中。它需要 PDO 连接或 数据源名称 (DSN)。此存储不支持阻塞,并期望使用 TTL 以避免死锁
1 2 3 4 5
use Symfony\Component\Lock\Store\PdoStore;
// a PDO instance or DSN for lazy connecting through PDO
$databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=app';
$store = new PdoStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
注意
此存储不支持低于 1 秒的 TTL。
存储值的表在首次调用 save() 方法时自动创建。您也可以通过在代码中调用 createTable() 方法显式创建此表。
DoctrineDbalStore
DoctrineDbalStore 将锁保存在 SQL 数据库中。它与 PdoStore 相同,但需要 Doctrine DBAL 连接 或 Doctrine DBAL URL。此存储不支持阻塞,并期望使用 TTL 以避免死锁
1 2 3 4 5
use Symfony\Component\Lock\Store\DoctrineDbalStore;
// a Doctrine DBAL connection or DSN
$connectionOrURL = 'mysql://myuser:[email protected]/app';
$store = new DoctrineDbalStore($connectionOrURL);
注意
此存储不支持低于 1 秒的 TTL。
当您运行命令时,将自动生成存储值的表
1
$ php bin/console make:migration
如果您希望自己创建表并且尚未创建,则可以通过调用 createTable() 方法显式创建此表。您也可以通过在代码中调用 configureSchema() 方法将此表添加到您的架构中
如果上游尚未创建表,则在首次调用 save() 方法时将自动创建。
PostgreSqlStore
PostgreSqlStore 使用 PostgreSQL 提供的 咨询锁。它需要 PDO 连接或 数据源名称 (DSN)。它支持原生阻塞以及共享锁
1 2 3 4 5
use Symfony\Component\Lock\Store\PostgreSqlStore;
// a PDO instance or DSN for lazy connecting through PDO
$databaseConnectionOrDSN = 'pgsql:host=localhost;port=5634;dbname=app';
$store = new PostgreSqlStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
与 PdoStore
相反,PostgreSqlStore
不需要表来存储锁,并且它不会过期。
DoctrineDbalPostgreSqlStore
DoctrineDbalPostgreSqlStore
使用 PostgreSQL 提供的 咨询锁。它与 PostgreSqlStore
相同,但需要 Doctrine DBAL 连接 或 Doctrine DBAL URL。它支持原生阻塞以及共享锁。
1 2 3 4 5
use Symfony\Component\Lock\Store\DoctrineDbalPostgreSqlStore;
// a Doctrine Connection or DSN
$databaseConnectionOrDSN = 'postgresql+advisory://myuser:[email protected]:5634/lock';
$store = new DoctrineDbalPostgreSqlStore($databaseConnectionOrDSN);
与 DoctrineDbalStore
相反,DoctrineDbalPostgreSqlStore
不需要表来存储锁,并且不会过期。
RedisStore
RedisStore
将锁保存在 Redis 服务器上,它需要实现 \Redis
, \RedisArray
, \RedisCluster
, \Relay\Relay
或 \Predis
类的 Redis 连接。此存储不支持阻塞,并期望使用 TTL 来避免锁停滞。
1 2 3 4 5 6
use Symfony\Component\Lock\Store\RedisStore;
$redis = new \Redis();
$redis->connect('localhost');
$store = new RedisStore($redis);
SemaphoreStore
SemaphoreStore
使用 PHP 信号量函数 来创建锁。
1 2 3
use Symfony\Component\Lock\Store\SemaphoreStore;
$store = new SemaphoreStore();
CombinedStore
CombinedStore
专为高可用性应用而设计,因为它同步管理多个存储(例如,多个 Redis 服务器)。当获取锁时,它会将调用转发到所有受管理的存储,并收集它们的响应。如果大多数存储已获取锁,则认为锁已获取。
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\Lock\Store\CombinedStore;
use Symfony\Component\Lock\Store\RedisStore;
use Symfony\Component\Lock\Strategy\ConsensusStrategy;
$stores = [];
foreach (['server1', 'server2', 'server3'] as $server) {
$redis = new \Redis();
$redis->connect($server);
$stores[] = new RedisStore($redis);
}
$store = new CombinedStore($stores, new ConsensusStrategy());
除了简单多数策略 (ConsensusStrategy
) 外,还可以使用 UnanimousStrategy
来要求在所有存储中都获取锁。
1 2 3 4
use Symfony\Component\Lock\Store\CombinedStore;
use Symfony\Component\Lock\Strategy\UnanimousStrategy;
$store = new CombinedStore($stores, new UnanimousStrategy());
警告
为了在使用 ConsensusStrategy
时获得高可用性,最小集群大小必须为三台服务器。这允许集群在单台服务器发生故障时继续工作(因为此策略要求锁在超过一半的服务器上被获取)。
ZookeeperStore
ZookeeperStore
将锁保存在 ZooKeeper 服务器上。它需要实现 \Zookeeper
类的 ZooKeeper 连接。此存储不支持阻塞和过期,但当 PHP 进程终止时,锁会自动释放。
1 2 3 4 5 6 7
use Symfony\Component\Lock\Store\ZookeeperStore;
$zookeeper = new \Zookeeper('localhost:2181');
// use the following to define a high-availability cluster:
// $zookeeper = new \Zookeeper('localhost1:2181,localhost2:2181,localhost3:2181');
$store = new ZookeeperStore($zookeeper);
注意
Zookeeper 不需要 TTL,因为用于锁定的节点是临时的,并在 PHP 进程终止时消失。
可靠性
该组件保证同一资源不会被锁定两次,只要该组件按以下方式使用。
远程存储
远程存储 (MemcachedStore, MongoDbStore, PdoStore, PostgreSqlStore, RedisStore 和 ZookeeperStore) 使用唯一令牌来识别锁的真正所有者。此令牌存储在 Key
对象中,并由 Lock
在内部使用。
每个并发进程都必须将 Lock
存储在同一服务器上。否则,两台不同的机器可能允许两个不同的进程获取同一个 Lock
。
警告
为了保证同一服务器始终安全,请勿在负载均衡器、集群或轮询 DNS 后使用 Memcached。即使主服务器宕机,调用也不得转发到备份或故障转移服务器。
过期存储
过期存储 (MemcachedStore, MongoDbStore, PdoStore 和 RedisStore) 保证锁仅在定义的持续时间内被获取。如果任务花费的时间更长才能完成,则锁可能会被存储释放并被其他人获取。
Lock
提供了几种方法来检查其健康状况。isExpired()
方法检查其生命周期是否结束,而 getRemainingLifetime()
方法返回其剩余生存时间(以秒为单位)。
使用上述方法,健壮的代码将是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// ...
$lock = $factory->createLock('pdf-creation', 30);
if (!$lock->acquire()) {
return;
}
while (!$finished) {
if ($lock->getRemainingLifetime() <= 5) {
if ($lock->isExpired()) {
// lock was lost, perform a rollback or send a notification
throw new \RuntimeException('Lock lost during the overall process');
}
$lock->refresh();
}
// Perform the task whose duration MUST be less than 5 seconds
}
警告
明智地选择 Lock
的生命周期,并检查其剩余生存时间是否足以执行任务。
警告
存储 Lock
通常需要几毫秒,但网络状况可能会大大增加该时间(最多几秒)。在选择正确的 TTL 时,请考虑这一点。
根据设计,锁存储在具有定义生命周期的服务器上。如果机器的日期或时间发生更改,则锁可能会比预期更早释放。
警告
为了保证日期不会更改,应禁用 NTP 服务,并在服务停止时更新日期。
FlockStore
通过使用文件系统,只要并发进程使用相同的物理目录来存储锁,此 Store
就是可靠的。
进程必须在同一台机器、虚拟机或容器上运行。更新 Kubernetes 或 Swarm 服务时要小心,因为在短时间内,可能有两个容器并行运行。
目录的绝对路径必须保持不变。小心可能随时更改的符号链接:Capistrano 和蓝/绿部署经常使用这种技巧。当该目录的路径在两次部署之间更改时要小心。
某些文件系统(例如某些类型的 NFS)不支持锁定。
警告
所有并发进程都必须通过在同一台机器上运行并使用锁目录的同一绝对路径来使用同一物理文件系统。
在 HTTP 上下文中使用 FlockStore
与多个前端服务器不兼容,除非确保同一资源始终在同一机器上锁定,或者使用配置良好的共享文件系统。
文件系统上的文件可能会在维护操作期间被删除。例如,清理 /tmp
目录或在机器重启后当目录使用 tmpfs
时。如果锁在进程结束后释放,则这不是问题,但在请求之间重用 Lock
的情况下,这是一个问题。
危险
如果锁必须在多个请求中重用,请勿将锁存储在易失性文件系统上。
MemcachedStore
Memcached 的工作方式是将项目存储在内存中。这意味着通过使用 MemcachedStore,锁不会被持久化,并且可能随时意外消失。
如果 Memcached 服务或托管它的机器重启,则每个锁都会丢失,而不会通知正在运行的进程。
警告
为了避免在重启后其他人获取锁,建议延迟服务启动并等待至少与最长锁 TTL 一样长的时间。
默认情况下,当 Memcached 服务需要空间添加新项目时,它使用 LRU 机制来删除旧条目。
警告
存储在 Memcached 中的项目数量必须受到控制。如果不可能,则应禁用 LRU,并将 Lock 存储在远离 Cache 的专用 Memcached 服务中。
当 Memcached 服务被共享并用于多种用途时,锁可能会被错误地删除。例如,PSR-6 clear()
方法的某些实现使用 Memcached 的 flush()
方法,该方法会清除并删除所有内容。
危险
不得调用 flush()
方法,或者应将锁存储在远离 Cache 的专用 Memcached 服务中。
MongoDbStore
警告
锁定的资源名称在锁集合的 _id
字段中被索引。请注意,MongoDB 中索引字段的值最大长度为 1024 字节,包括结构开销。
必须使用 TTL 索引来自动清理过期的锁。这样的索引可以手动创建
1 2 3 4
db.lock.createIndex(
{ "expires_at": 1 },
{ "expireAfterSeconds": 0 }
)
或者,可以调用 MongoDbStore::createTtlIndex(int $expireAfterSeconds = 0)
方法一次,以便在数据库设置期间创建 TTL 索引。阅读更多关于 MongoDB 中 通过设置 TTL 从集合中使数据过期 的信息。
提示
MongoDbStore
将尝试自动创建 TTL 索引。如果您已手动处理 TTL 索引创建,建议将构造函数选项 gcProbability
设置为 0.0
以禁用此行为。
警告
此存储依赖于所有 PHP 应用程序和数据库节点都具有同步时钟,以便锁过期在正确的时间发生。为确保锁不会过早过期;应在 expireAfterSeconds
中设置足够的额外时间的锁 TTL,以考虑节点之间的任何时钟漂移。
writeConcern
和 readConcern
未由 MongoDbStore 指定,这意味着集合的设置将生效。readPreference
对于所有查询都是 primary
。阅读更多关于 MongoDB 中 副本集读取和写入语义 的信息。
PdoStore
PdoStore
依赖于 SQL 引擎的 ACID 属性。
警告
在配置了多个主节点的集群中,确保写入同步传播到每个节点,或者始终使用同一节点。
警告
某些 SQL 引擎(如 MySQL)允许禁用唯一约束检查。确保情况并非如此 SET unique_checks=1;
。
为了清除旧锁,此存储使用当前日期时间来定义过期日期参考。此机制依赖于所有服务器节点都具有同步时钟。
警告
为确保锁不会过早过期;应设置具有足够额外时间的 TTL,以考虑节点之间的任何时钟漂移。
PostgreSqlStore
PostgreSqlStore
依赖于 PostgreSQL 数据库的 咨询锁 属性。这意味着通过使用 PostgreSqlStore,如果客户端由于任何原因无法解锁,锁将在会话结束时自动释放。
如果 PostgreSQL 服务或托管它的机器重启,则每个锁都会丢失,而不会通知正在运行的进程。
如果 TCP 连接丢失,PostgreSQL 可能会释放锁,而不会通知应用程序。
RedisStore
Redis 的工作方式是将项目存储在内存中。这意味着通过使用 RedisStore,锁不会被持久化,并且可能随时意外消失。
如果 Redis 服务或托管它的机器重启,则每个锁都会丢失,而不会通知正在运行的进程。
警告
为了避免在重启后其他人获取锁,建议延迟服务启动并等待至少与最长锁 TTL 一样长的时间。
提示
Redis 可以配置为将项目持久化到磁盘,但此选项会减慢服务上的写入速度。这可能与其他服务器用途相悖。
当 Redis 服务被共享并用于多种用途时,锁可能会被错误地删除。
危险
不得调用 FLUSHDB
命令,或者应将锁存储在远离 Cache 的专用 Redis 服务中。
CombinedStore
组合存储允许跨多个后端存储锁。认为锁定机制会更可靠是一个常见的错误。这是错误的。CombinedStore
最多只能与所有受管理存储中最不可靠的存储一样可靠。一旦一个受管理存储返回错误信息,CombinedStore
将不可靠。
警告
所有并发进程都必须使用相同的配置,具有相同数量的受管理存储和相同的端点。
提示
与其使用 Redis 或 Memcached 服务器集群,不如使用 CombinedStore
,每个受管理存储使用单个服务器。
SemaphoreStore
信号量由内核级别处理。为了可靠,进程必须在同一台机器、虚拟机或容器上运行。更新 Kubernetes 或 Swarm 服务时要小心,因为在短时间内,可能有两个容器并行运行。
警告
所有并发进程都必须使用同一台机器。在新机器上启动并发进程之前,请检查旧机器上的其他进程是否已停止。
警告
当在 systemd 上以非系统用户和 RemoveIPC=yes
选项(默认值)运行时,当该用户注销时,锁会被 systemd 删除。检查进程是否以系统用户(UID <= SYS_UID_MAX)运行,其中 SYS_UID_MAX
在 /etc/login.defs
中定义,或者在 /etc/systemd/logind.conf
中设置 RemoveIPC=off
选项。
ZookeeperStore
ZookeeperStore
的工作方式是通过将锁维护为服务器上的临时节点。这意味着通过使用 ZookeeperStore,如果客户端由于任何原因无法解锁,锁将在会话结束时自动释放。
如果 ZooKeeper 服务或托管它的机器重启,则每个锁都会丢失,而不会通知正在运行的进程。
提示
要使用 ZooKeeper 的高可用性功能,您可以设置一个多服务器集群,以便在其中一台服务器宕机的情况下,大多数服务器仍将保持运行并服务请求。集群中所有可用的服务器都将看到相同的状态。
注意
由于此存储不支持多级节点锁,因为清理中间节点会成为开销,因此所有锁都在根级别维护。
概述
更改存储的配置应非常谨慎。例如,在新版本部署期间。当具有旧配置的旧进程仍在运行时,不得启动具有新配置的进程。