锁组件
编辑本页警告:您正在浏览的文档欧宝官网下载appob娱乐下载Symfony 5.0,现已不再维护。
读本页的更新版本用于Syob娱乐下载mfony 6.2(当前稳定版本)。
锁组件
锁组件创建和管理锁,一种提供对共享资源的独占访问的机制。
安装
1
$作曲家需要symfony/lockob娱乐下载
请注意
如果在Symfony应用程序外部安装此组件,则必须要求ob娱乐下载供应商/ autoload.php
文件,以启用Composer提供的类自动加载机制。读这篇文章欲知详情。
使用
锁用于保证对某些共享资源的独占访问。例如,ob娱乐下载在Symfony应用程序中,您可以使用锁来确保一个命令不会在同一时间(在相同或不同的服务器上)执行多次。
类创建锁LockFactory类,这又需要另一个类来管理锁的存储:
1 2 3 4 5
使用ob娱乐下载\组件\锁\LockFactory;使用ob娱乐下载\组件\锁\商店\SemaphoreStore;$商店=新SemaphoreStore ();$工厂=新LockFactory ($商店);
方法创建锁createLock ()方法。它的第一个参数是表示锁定资源的任意字符串。然后,调用获得()方法将尝试获取锁:
1 2 3 4 5 6 7 8 9
/ /……$锁=$工厂->createLock (“pdf-invoice-generation”);如果($锁->获得()){//资源“pdf-invoice-generation”被锁定。//您可以在这里安全地计算和生成发票。$锁->release ();}
如果无法获取锁,则该方法返回假
.的获得()
方法可以安全地重复调用,即使已经获得了锁。
请注意
与其他实现不同的是,即使是为同一资源创建的锁实例,Lock Component也能区分它们。如果一个锁必须被多个服务使用,那么它们应该共享同一个锁锁
实例返回的LockFactory: createLock
方法。
提示
如果您没有显式地释放锁,它将在实例销毁时自动释放。在某些情况下,跨多个请求锁定一个资源可能很有用。属性的第三个参数,可禁用自动释放行为createLock ()
方法假
.
阻塞锁
缺省情况下,当无法获取锁时,将使用收购
方法返回假
立即。要等待(无限期地)直到可以创建锁,请通过真正的
作为论证获得()
方法。这叫做阻塞锁因为在获得锁之前,应用程序的执行将停止。
一些内置的商店
类支持此特性。如果没有,可以用RetryTillSaveStore
类:
1 2 3 4 5 6 7 8 9 10
使用ob娱乐下载\组件\锁\LockFactory;使用ob娱乐下载\组件\锁\商店\RedisStore;使用ob娱乐下载\组件\锁\商店\RetryTillSaveStore;$商店=新RedisStore (新\ Predis \客户端(“tcp: / / localhost: 6379”));$商店=新RetryTillSaveStore ($商店);$工厂=新LockFactory ($商店);$锁=$工厂->createLock (“notification-flush”);$锁->获得(真正的);
到期的锁
远程创建的锁很难管理,因为没有远程的方法商店
以了解储物柜进程是否仍然存在。由于bug,致命错误或分割错误,不能保证release ()
方法,这将导致资源被无限锁定。
在这种情况下,最好的解决办法就是创造到期的锁,在经过一段时间后自动释放(对于活着的时间).属性的第二个参数,单位为秒createLock ()
方法。方法也可以提前释放这些锁release ()
方法。
处理过期锁时最棘手的部分是选择正确的TTL。如果它太短,其他进程可能会在完成任务之前获得锁;如果时间过长,进程在调用release ()
方法,资源将保持锁定直到超时:
1 2 3 4 5 6 7 8 9 10
/ /……//创建一个持续30秒的过期锁$锁=$工厂->createLock (“charts-generation”,30.);$锁->获得();试一试{//在30秒内完成一个任务}最后{$锁->release ();}
提示
为了避免锁处于锁定状态,建议将作业封装在try/catch/finally块中,以便始终尝试释放即将到期的锁。
对于长时间运行的任务,最好从一个不太长的TTL开始,然后使用refresh ()方法将TTL重置为原始值:
12 3 4 5 6 7 8 9 10 11 12 13 14
/ /……$锁=$工厂->createLock (“charts-generation”,30.);$锁->获得();试一试{而(!$完成了){//完成工作的一小部分。//更新锁30秒。$锁->refresh ();}}最后{$锁->release ();}
提示
类的参数传递自定义TTL,这是长时间运行任务的另一个有用技术refresh ()
方法修改默认锁TTL:
1 2 3 4 5 6 7
$锁=$工厂->createLock (“charts-generation”,30.);/ /……//刷新锁30秒$锁->refresh ();/ /……//刷新锁600秒(下一次refresh()调用将再次为30秒)$锁->刷新(600);
该组件还提供了两个与过期锁相关的有用方法:getExpiringDate ()
(返回零
或者一个\ DateTimeImmutable
对象),isExpired ()
(返回一个布尔值)。
锁的主人
对象第一次获得的锁为[1]_锁
获取它的实例。如果需要检查电流是否锁
实例仍然是锁的所有者,您可以使用isAcquired ()
方法:
1 2 3
如果($锁->isAcquired ()) {//我们仍然拥有这把锁}
由于一些锁存储有过期的锁(如上所述),实例可能会丢失它自动获得的锁:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
//如果我们自己无法获取,就意味着其他进程已经在处理它了如果(!$锁->获得()){返回;}$这->beginTransaction ();//执行一个很长的进程,可能超过锁的生存时间如果($锁->isAcquired ()) {//一切正常,在此期间没有其他实例获得锁,我们是安全的$这->commit ();}其他的{/ /懒汉!我们的锁显然已经超过了TTL,另一个进程已经开始//在此期间,所以它对我们来说是不安全的。$这->rollback ();扔新\异常(“过程失败”);}
谨慎
一个常见的缺陷可能是使用isAcquired ()
方法检查某个锁是否已被任何进程获取。正如你在这个例子中看到的,你必须使用获得()
对于这个。的isAcquired ()
方法用于检查锁是否已由当前进程只有!
-
..[1]从技术上讲,锁的真正所有者是那些共享相同实例的人
关键
, -
不
锁
.但从用户的角度来看,关键
是内部的,您可能只与锁
实例,这样更容易想到锁
实例作为锁的所有者。
可用的商店
在其中创建和管理锁商店
,它们是实现的类PersistingStoreInterface可选地,BlockingStoreInterface.
该组件包括以下内置存储类型:
商店 | 范围 | 阻塞 | 即将到期的 |
---|---|---|---|
FlockStore | 当地的 | 是的 | 没有 |
MemcachedStore | 远程 | 没有 | 是的 |
PdoStore | 远程 | 没有 | 是的 |
RedisStore | 远程 | 没有 | 是的 |
SemaphoreStore | 当地的 | 是的 | 没有 |
ZookeeperStore | 远程 | 没有 | 没有 |
FlockStore
FlockStore使用本地计算机上的文件系统创建锁。它不支持过期,但是当锁对象超出作用域时,锁会自动释放,并由垃圾收集器释放(例如当PHP进程结束时):
1 2 3 4 5
使用ob娱乐下载\组件\锁\商店\FlockStore;//参数是创建锁的目录的路径//如果没有,则在内部使用sys_get_temp_dir()。$商店=新FlockStore (/ var /商店的);
谨慎
注意,某些文件系统(例如某些类型的NFS)不支持锁定。在这种情况下,最好使用本地磁盘驱动器上的目录或基于PDO、Redis或Memcached的远程存储。
MemcachedStore
MemcachedStore将锁保存在Memcached服务器上,它需要一个Memcached连接来实现\ Memcached
类。这个存储不支持阻塞,并期望TTL来避免锁被卡住:
1 2 3 4 5 6
使用ob娱乐下载\组件\锁\商店\MemcachedStore;$memcached=新\ Memcached ();$memcached->addServer (“localhost”,11211);$商店=新MemcachedStore ($memcached);
请注意
Memcached不支持TTL小于1秒。
PdoStore
PdoStore将锁保存在SQL数据库中。它需要PDO连接,原则DBAL连接,或数据源名称(DSN).这个存储不支持阻塞,并期望TTL来避免锁被卡住:
1 2 3 4 5
使用ob娱乐下载\组件\锁\商店\PdoStore;// PDO, Doctrine DBAL连接或通过PDO进行延迟连接的DSN$databaseConnectionOrDSN=“mysql:主机= 127.0.0.1;dbname =锁';$商店=新PdoStore ($databaseConnectionOrDSN, [“db_username”= >“myuser”,“db_password”= >“我的密码”]);
请注意
此商店不支持TTL低于1秒。
在数据库中存储锁之前,必须创建存储信息的表。存储提供了一个方法不知道()根据所使用的数据库引擎设置此表:
1 2 3 4 5
试一试{$商店->不知道();}抓(\ PDOException$异常){//由于某些原因无法创建表}
在生产环境中设置表的一个好方法是调用不知道()
方法,然后生成一个数据库迁移:
1 2
$PHP bin/控制台原则:迁移:diff$PHP bin/控制台原则:迁移:迁移
RedisStore
RedisStore将锁保存在Redis服务器上,它需要一个Redis连接来实现\复述,
,\ RedisArray
,\ RedisCluster
或\ Predis
类。这个存储不支持阻塞,并期望TTL来避免锁被卡住:
1 2 3 4 5 6
使用ob娱乐下载\组件\锁\商店\RedisStore;$复述,=新\复述();$复述,->连接(“localhost”);$商店=新RedisStore ($复述,);
SemaphoreStore
SemaphoreStore使用PHP信号量函数要创建锁:
1 2 3
使用ob娱乐下载\组件\锁\商店\SemaphoreStore;$商店=新SemaphoreStore ();
CombinedStore
组合商店是为高可用性应用程序设计的,因为它同步管理多个商店(例如,几个Redis服务器)。当获取锁时,它将调用转发给所有托管存储,并收集它们的响应。如果简单多数商店已经获得该锁,则该锁被视为已获得;否则如未获得:
12 3 4 5 6 7 8 9 10 11 12 13
使用ob娱乐下载\组件\锁\商店\CombinedStore;使用ob娱乐下载\组件\锁\商店\RedisStore;使用ob娱乐下载\组件\锁\策略\ConsensusStrategy;$商店= [];foreach([“server1”,server2的,“server3”]作为$服务器){$复述,=新\复述();$复述,->连接($服务器);$商店[] =新RedisStore ($复述,);}$商店=新CombinedStore ($商店,新ConsensusStrategy ());
而不是简单多数策略(ConsensusStrategy
)一个UnanimousStrategy
可用于要求在所有存储中获取锁。
谨慎
为了在使用时获得高可用性ConsensusStrategy
时,最小集群大小必须为3台服务器。这允许集群在单个服务器故障时继续工作(因为此策略要求在超过一半的服务器中获得锁)。
ZookeeperStore
ZookeeperStore保存锁动物园管理员服务器。它需要一个ZooKeeper连接来实现\动物园管理员
类。这个store不支持阻塞和过期,但是当PHP进程终止时,锁会自动释放:
1 2 3 4 5 6 7
使用ob娱乐下载\组件\锁\商店\ZookeeperStore;$动物园管理员=新\管理员(“localhost: 2181”);//使用以下命令定义高可用性集群:// $zookeeper = new \ zookeeper ('localhost1:2181,localhost2:2181,localhost3:2181');$商店=新ZookeeperStore ($动物园管理员);
请注意
Zookeeper不需要TTL,因为用于锁定的节点是短暂的,当PHP进程终止时就会死亡。
可靠性
该组件保证只要以以下方式使用该组件,同一资源就不会被锁定两次。
远程存储
远程存储(MemcachedStore,PdoStore,RedisStore而且ZookeeperStore)使用唯一令牌来识别锁的真正所有者。标记存储在关键对象在内部使用锁
因此这个键不能在进程(session, caching, fork,…)之间共享。
谨慎
不要在进程之间共享密钥。
每个并发进程必须存储锁
在同一台服务器上。否则两台不同的机器可能允许两个不同的进程获得相同的数据锁
.
谨慎
为了保证同一台服务器始终是安全的,不要在LoadBalancer、集群或轮询DNS后面使用Memcached。即使主服务器关闭,也不能将调用转发到备份或故障转移服务器。
到期的商店
即将到期的储存区(MemcachedStore,PdoStore而且RedisStore)保证只在规定的时间内获得锁。如果任务需要更长的时间才能完成,那么锁可以由存储释放,并由其他人获取。
的锁
提供几种检查其运行状况的方法。的isExpired ()
方法检查其生命周期是否结束getRemainingLifetime ()
方法返回以秒为单位的时间。
使用上述方法,更健壮的代码将是:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/ /……$锁=$工厂->createLock (“invoice-publication”,30.);$锁->获得();而(!$完成了){如果($锁->getRemainingLifetime () < =5){如果($锁->isExpired ()) {//锁丢失,执行回滚或发送通知扔新\ RuntimeException (“锁在整个过程中丢失”);}$锁->refresh ();}//执行耗时小于5分钟的任务}
谨慎
明智地选择你的一生锁
并检查其剩余的离开时间是否足够执行任务。
谨慎
存储锁
通常需要几毫秒,但网络条件可能会大大增加这个时间(最多几秒)。在选择正确的TTL时要考虑到这一点。
按照设计,锁存储在具有定义的生命周期的服务器中。如果机器的日期或时间发生了变化,锁可能会比预期更快地释放。
谨慎
为了保证日期不会改变,NTP服务应该被禁用,并且在服务停止时应该更新日期。
FlockStore
通过使用文件系统,这商店
只要并发进程使用相同的物理目录来存储锁,它就是可靠的。
进程必须运行在同一台机器、虚拟机或容器上。更新Kubernetes或Swarm服务时要小心,因为在短时间内,可能会有两个并行运行的容器。
目录的绝对路径必须保持不变。小心可能随时改变的符号链接:Capistrano和蓝/绿部署经常使用这个技巧。当到该目录的路径在两次部署之间发生变化时要小心。
某些文件系统(例如某些类型的NFS)不支持锁定。
谨慎
所有并发进程必须使用相同的物理文件系统,运行在同一台机器上,并使用相同的绝对路径到locks目录。
根据定义,用法FlockStore
在HTTP上下文中与多个前端服务器不兼容,除非确保同一资源始终锁定在同一台机器上,或者使用配置良好的共享文件系统。
文件系统中的文件可以在维护操作时删除。例如,清理/ tmp
目录或当目录使用tmpfs时重新启动计算机后。如果在进程结束时释放锁,这不是问题,但如果发生锁
在请求之间重用。
谨慎
如果需要在多个请求中重用volatile文件系统上的锁,则不要将它们存储在volatile文件系统上。
MemcachedStore
Memcached的工作方式是将项存储在内存中。这意味着通过使用MemcachedStore锁没有被持久化,并且可能在任何时候错误地消失。
如果Memcached服务或承载它的机器重新启动,每个锁都将丢失,而不会通知正在运行的进程。
谨慎
为了避免重启后被其他人获得锁,建议延迟服务启动,等待时间至少等于最长锁TTL。
默认情况下,当服务需要空间来添加新项时,Memcached使用LRU机制删除旧项。
谨慎
必须控制存储在Memcached中的项的数量。如果不可能,应该禁用LRU,并将Lock存储在专用的Memcached服务中,远离Cache。
当Memcached服务被共享并用于多个用途时,锁可能会被错误地删除。例如PSR-6的一些实现clear ()
方法使用Memcached的冲洗()
清除和删除所有内容的方法。
谨慎
该方法冲洗()
不能被调用,或者锁应该存储在专用的Memcached服务中,远离Cache。
PdoStore
PdoStore依赖酸SQL引擎属性。
谨慎
在配置了多个主的集群中,请确保同步地将写操作传播到每个节点,或者始终使用相同的节点。
谨慎
一些SQL引擎(如MySQL)允许禁用唯一约束检查。确保不是这种情况设置unique_checks = 1;
.
为了清除旧锁,该存储使用当前datetime来定义过期日期引用。该机制依赖于所有服务器节点具有同步时钟。
谨慎
确保锁不会过早过期;ttl应该设置足够的额外时间,以考虑节点之间的任何时钟漂移。
RedisStore
Redis的工作方式是将项目存储在内存中。这意味着通过使用RedisStore锁没有被持久化,并且可能在任何时候错误地消失。
如果Redis服务或承载它的机器重新启动,每个锁都将丢失,而不通知正在运行的进程。
谨慎
为了避免重启后被其他人获得锁,建议延迟服务启动,等待时间至少等于最长锁TTL。
提示
Redis可以配置为将项目持久化在磁盘上,但是这个选项会减慢对服务的写操作。这可能与服务器的其他用途相冲突。
当Redis服务被共享并用于多个用途时,锁可能会被错误地删除。
谨慎
命令FLUSHDB
必须不被调用,或者锁应该存储在一个专用的Redis服务远离缓存。
CombinedStore
合并存储允许跨多个后端存储锁。认为锁定机制会更可靠是一个常见的错误。这是错误的。的CombinedStore
最好的情况下,将与所有托管存储中最不可靠的存储一样可靠。一旦某个托管存储返回错误信息,则CombinedStore
不可靠。
谨慎
所有并发进程必须使用相同的配置,具有相同数量的托管存储和相同的端点。
提示
与其使用Redis或Memcached服务器集群,不如使用CombinedStore
每个托管存储都有一个服务器。
SemaphoreStore
信号量由内核级处理。为了保证可靠性,进程必须运行在同一台机器、虚拟机或容器上。更新Kubernetes或Swarm服务时要小心,因为在短时间内,可能会有两个并行运行的容器。
谨慎
所有并发进程必须使用同一台机器。在新机器上启动并发进程之前,请检查旧机器上的其他进程是否已停止。
谨慎
使用非系统用户和选项在systemd上运行时RemoveIPC = yes
(默认值),当用户注销时,systemd将删除锁。检查进程是否使用系统用户(UID <= SYS_UID_MAX)运行SYS_UID_MAX
中定义的/etc/login.defs
,或设置该选项RemoveIPC =了
在/etc/systemd/logind.conf
.
ZookeeperStore
zookeeper的工作方式是将锁作为服务器上的临时节点来维护。这意味着通过使用ZookeeperStore锁将在会话结束时自动释放,以防客户端因任何原因无法解锁。
如果ZooKeeper服务或承载它的机器重新启动,每个锁都会丢失,而不会通知正在运行的进程。
提示
为了使用ZooKeeper的高可用性特性,你可以设置多个服务器的集群,这样在其中一个服务器宕机的情况下,大多数服务器仍然是正常的,并为请求服务。集群中的所有可用服务器都将看到相同的状态。
请注意
由于此存储不支持多级节点锁,因此清理中间节点成为一种开销,因此所有锁都在根级别维护。
整体
更改存储的配置应该非常谨慎。例如,在部署新版本期间。当具有旧配置的旧进程仍在运行时,不能启动具有新配置的进程。