我想通过使用Symfony Lock组件来防止用户两次发出相同的请求。因为现在用户可以两次单击一个链接(是否意外?),并创建重复的实体。我想使用唯一实体约束,该约束本身并不针对争用条件提供保护。
Symfony Lock组件似乎没有按预期工作。当我在页面开头创建一个锁,并同时打开页面两次时,两个请求都可以获得锁。当我在标准和匿名浏览器窗口中打开测试页时,第二个请求没有获得锁。但我在文档中找不到任何关于此操作与会话关联的信息。我在一个新的项目中创建了一个小的测试文件来隔离这个问题。
<?php
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Routing\Annotation\Route;
class LockTest extends AbstractController
{
/**
* @Route("/test")
* @Template("lock/test.html.twig")
*/
public function test(LockFactory $factory): array
{
$lock = $factory->createLock("test");
$acquired = $lock->acquire();
dump($lock, $acquired);
sleep(2);
dump($lock->isAcquired());
return ["message" => "testing"];
}
}
1条答案
按热度按时间5hcedyr01#
我稍微重写了您的控制器,如下所示(使用symfony 5.4和php 8.1):
它等待释放锁,并计算控制器等待获取锁的时间。
我用curl在caddy服务器上运行了两个请求。
输出确认一个请求被延迟,而第一个请求使用获取的锁休眠。
因此,锁的作用是防止并发请求。
如果锁未阻塞:
输出为:
请注意,第二个锁是如何未获取的。您应该使用此标志来拒绝用户的请求,并显示错误,而不是创建重复的实体。
如果两个请求间隔足够大,可以依次获得锁,则可以检查实体是否存在(因为它有时间完全提交到数据库)并返回错误。
尽管有这些令人鼓舞的结果,但该文件提到了以下注意事项:
与其他实现不同,锁组件区分锁示例,即使它们是为同一资源创建的。这意味着对于给定的作用域和资源,一个锁示例可以被多次获取。如果一个锁必须由多个服务使用,则它们应该共享由LockFactory::createLock方法返回的同一个锁示例。
我知道两个不同工厂获得的两个锁不应该互相阻塞。除非注解过时或措辞错误,否则在某些情况下似乎可能有不工作的锁。但使用上面的测试代码则不然。
流式响应
当锁定超出范围时,就会解除锁定。
作为一种特殊情况,当返回
StreamedResponse
时,当控制器返回响应时,锁将超出范围。要在生成响应时保持锁,必须将其传递给
StreamedResponse
执行的函数:上面的代码每50秒刷新一次锁,以减少与存储引擎(如redis)的通信时间。
如果php进程突然死亡,锁最多保持锁定60秒。