在两个守护进程中的同一个数据库上使用 Doctrine Entity Manager

Using Doctrine Entity Manager on same database in two daemons

我正在 运行设置两个守护进程,它们基本上几乎每秒 24/7 都会询问外部服务。他们每个人在每次循环后都在同一个本地数据库中插入或更新内容,但他们处理相同实体的不同对象。

因为他们 运行 24/7,经过一些测试后,我决定在每次循环后清除实体管理器,以避免拥有大量托管实体和大量内存使用。

所以,在他们两个中,我 运行 在每个循环之后都是这样的:

$this->entityManager->flush();
....
$this->entityManager->clear(MyClass:class);
$this->entityManager->clear(MyOtherClass:class);
....

我想问的是:如果DaemonA 清除了实体而DaemonB 还没有刷新持久化的变化,会发生什么?当 DaemonA 刷新时,它会以任何方式影响 DaemonB 中的实体吗?有些物体会丢失吗?有些会被复制吗?如果是这样,我该怎么做才能避免这种事情?

正如我所说,它们在同一实体的不同对象上工作,例如,DaemonA 在 MyOtherClass 对象 1、2、3 上工作,而 DaemonB 在 MyOtherClass 对象 4、5、6 上工作。

两个守护进程都是这样构造的 Symfony 命令:

class DaemonA extends Command
{
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
        parent::__construct();
    }
    ...
 }

这里有很多问题,所以让我们逐步解决它们。

在我们开始之前,请记住 Doctrine 在内部是如何工作的:如果通过查询或存储库请求一个实体或一组实体,Doctrine 从数据库加载实体数据,创建实体,用数据填充它们,跟踪更改并将更改同步回数据库。 Doctrine 实体有 states,通常它们处于 managed 状态,除非你分离它们。当您清除实体管理器时,实体变为 分离

现在,回答您的问题:

if DaemonA clears the entities and DaemonB hasn't flushed the persisted changes yet, what happens?

清除实体管理器只意味着实体变得分离,如果不再引用它们,就会被垃圾收集(我认为)。在数据库级别,这是无关紧要的。

When DaemonA flushes, does it affect in any way the entities in DaemonB?

是的,但不是在 DaemonB 处于 运行 并且不从数据库重新加载实体时。如果 DaemonA 修改实体,而 DaemonB 在重新加载它们之前修改相同的实体,DaemonB 的修改将持续存在。

Could some get duplicated?

仅当您保留分离的实体时(尽管它们会有一个新的 ID)。然而,持久化分离的实体无论如何都没有意义。

If so, what can I do to avoid this kind of things?

锁和交易!

  1. 包含多个查询的数据库的每一次修改都必须包含在一个事务中。如果出现问题或并发请求修改数据,这可以避免不一致。在 PHP 级别,交易应该再次包装在 try/catch 块中。

  2. 如果您正在修改实体,请将其锁定。学说支持different types of locking;选择最适合您的方案。

您的某个守护程序中的代码可能如下所示:

try
{
    $em->beginTransaction();

    $entity = $em->find($entityClassName, $id);

    // lock the entity for all reading and writing. 
    $em->lock($entity, LockMode::PESSIMISTIC_WRITE);


    $em->flush();
    $em->commit();
}
catch (Exception $e)
{
    $em->rollback();

    throw $e;
}

请注意,根据您的锁定策略和系统的一般实施方式,守护进程可能会通过锁定数据库来相互阻塞,直至系统资源耗尽。

例如悲观写锁更安全(它确保其他进程在修改完成之前不会读取数据),但其他进程将不得不等待直到释放锁。

注意在重负载场景下进行测试!