作为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);
}
这种方法的缺点:
- 如果在
sales_flat_order_grid
表中使用了您的字段,则还必须更新该表。这是Magento的模型通常会为您做的事情。您可以添加一个JOIN
,以便在单个UPDATE
查询中处理此问题。 - 它绕过了Magento模型,这意味着像
sales_order_save_commit_after
和sales_order_save_after
这样的观察者不会被触发。
优势:
- 运行单个更新比保存整个模型要快得多。
选项B:您可以使用锁定机制,例如 Mage_Index_Model_Lock
,这是Magento在刷新索引时所做的。
工作原理:假设我们正在处理订单号 100002185
。在此示例中,流程 A 和 B 可互换。
- 进程 A 启动并检查是否有锁定
automation_order_id_100002185
。 - 进程 A 找不到锁,因此它为
automation_order_id_100002185
设置锁并开始执行其工作。 - 进程 B 在进程 A 完成之前启动,并检查锁定
automation_order_id_100002185
。 - 进程 B 找到一个锁,因此它休眠 3 秒钟。
- 进程 A 仍在工作。进程 B 在休眠 3 秒后再次检查锁,但进程 A 尚未释放锁,因此它又进行了 3 秒休眠。
- 进程 A 完成其工作,并释放锁。
- 进程 B 在休眠 3 秒后再次检查锁。这次没有锁,所以进程 B 设置一个锁,并开始做它的工作。
- 进程 B 完成其工作并释放锁。
这种方法的缺点:
- 它牺牲了速度。如果每个进程更新的字段不同、不相关且独立,则它们可以并行执行其工作。
- 灾难性的服务器故障(或重新启动(可能导致锁永远不会释放。
- 如果锁从未释放,则进程可能会无限期休眠,并且其工作可能永远无法完成。这可能会导致进程堆叠并最终使服务器崩溃,从而导致其他进程的连锁反应。
- 如果服务器崩溃,并且您没有某种可以依赖的队列,则您的处理可能永远不会真正完成。Magento 2使用RabbitMQ来解决这个问题,但你可以使用数据库表和cron脚本实现类似的机制。