解决Magento ORM竞争条件


Solving Magento ORM race condition

作为Magento专业服务和模块开发人员,我们需要实现Magento订单处理工作流程,其中订单信息从2个单独的并发进程(第三方支付处理通知处理程序和客户到订单分配处理程序(加载,更新和存储。当前实现工作正常,但该进程容易受到争用条件问题的影响。

进程 A: load((A -> updateFields1(( -> save((A

进程 B: load((B -> updateFields2(( -> save((B

如果在 load((B 之后但在 save((B 之前调用 load((A,则其中一个进程会覆盖并发进程设置的值。

Magento框架是否提供了一种可能性或一些常见的做法来处理竞争条件,前提是方法updateField1((和updateField2((中更新的字段完全不同?

基本上有两个选项可以避免这种竞争条件。他们都有取舍。

选项 A:进行两个单独的UPDATE查询来更新这些字段,并让数据库服务器为您处理锁定。Zend的数据库框架已经内置了进行此类更新的方法。下面是两个示例函数,可帮助您入门:

function paymentProcessingUpdate($incrementId, $paymentId) {
    $db = Mage::getSingleton('core/resource')->getConnection('core_write');
    $table = 'sales_flat_order';
    $data = array('payment_id' => $paymentId);
    $where = $db->quoteInto('increment_id = ?', $incrementId);
    $db->update($table, $data, $where);
}
function assignCustomerToOrder($incrementId, $customerId) {
    $db = Mage::getSingleton('core/resource')->getConnection('core_write');
    $table = 'sales_flat_order';
    $data = array('customer_id' => $customerId);
    $where = $db->quoteInto('increment_id = ?', $incrementId);
    $db->update($table, $data, $where);
}

这种方法的缺点:

  1. 如果在sales_flat_order_grid表中使用了您的字段,则还必须更新该表。这是Magento的模型通常会为您做的事情。您可以添加一个JOIN,以便在单个UPDATE查询中处理此问题。
  2. 它绕过了Magento模型,这意味着像sales_order_save_commit_aftersales_order_save_after这样的观察者不会被触发。

优势:

  1. 运行单个更新比保存整个模型要快得多。

选项B:您可以使用锁定机制,例如 Mage_Index_Model_Lock ,这是Magento在刷新索引时所做的。

工作原理:假设我们正在处理订单号 100002185 。在此示例中,流程 A 和 B 可互换。

  1. 进程 A 启动并检查是否有锁定automation_order_id_100002185
  2. 进程 A 找不到锁,因此它为automation_order_id_100002185设置锁并开始执行其工作。
  3. 进程 B 在进程 A 完成之前启动,并检查锁定automation_order_id_100002185
  4. 进程 B 找到一个锁,因此它休眠 3 秒钟。
  5. 进程 A 仍在工作。进程 B 在休眠 3 秒后再次检查锁,但进程 A 尚未释放锁,因此它又进行了 3 秒休眠。
  6. 进程 A 完成其工作,并释放锁。
  7. 进程 B 在休眠 3 秒后再次检查锁。这次没有锁,所以进程 B 设置一个锁,并开始做它的工作。
  8. 进程 B 完成其工作并释放锁。

这种方法的缺点:

  1. 它牺牲了速度。如果每个进程更新的字段不同、不相关且独立,则它们可以并行执行其工作。
  2. 灾难性的服务器故障(或重新启动(可能导致锁永远不会释放。
  3. 如果锁从未释放,则进程可能会无限期休眠,并且其工作可能永远无法完成。这可能会导致进程堆叠并最终使服务器崩溃,从而导致其他进程的连锁反应。
  4. 如果服务器崩溃,并且您没有某种可以依赖的队列,则您的处理可能永远不会真正完成。Magento 2使用RabbitMQ来解决这个问题,但你可以使用数据库表和cron脚本实现类似的机制。