我需要在PHP中使用互斥锁或信号量,这让我很害怕。为了澄清,我并不害怕编写无死锁的代码,这些代码可以正常同步或者担心并发编程的危险,但是PHP处理边缘情况的程度如何。
快速背景:编写位于用户和第三方信用卡网关之间的信用卡处理程序界面。需要防止重复请求,并且已经有一个适用的系统,但是如果用户点击提交(w / out JS启用,所以我无法为他们禁用按钮)相隔毫秒,一个竞争条件接着我的PHP脚本没有意识到已经发出了重复的请求。需要一个信号量/互斥量,这样我才能确保每个唯一的事务只有一个成功的请求。
我通过PHP-FPM在多核Linux机器上运行多个进程,在nginx后面运行PHP。我想确定
是的,我知道。非常基本的问题,认为任何其他软件都不存在适当的解决方案是愚蠢的。但这是PHP,它肯定不会考虑并发性,它经常崩溃(取决于你加载的扩展),并且处于易变的环境中(PHP-FPM和网络上)。
关于(1),我假设如果PHP使用POSIX函数,这些条件在SMP i686机器上都适用。至于(2),我从简要略读文档中看到,有一个参数决定了这种行为(虽然为什么人们希望PHP不释放互斥锁是会话被杀死我不明白)。但是(3)是我的主要关注点,我不知道是否可以安全地假设php-fpm正确地为我处理所有边缘情况。我(显然)并不想要死锁,但我不确定我是否可以信任PHP,永远不会让我的代码处于无法获取互斥锁的状态,因为抓住它的会话要么优雅,要么非正常地终止。< / p>
我考虑过使用MySQL LOCK TABLES
方法,但是还有更多的疑问,因为虽然我相信MySQL锁比PHP锁更多,但我担心如果PHP中止请求(带* out *崩溃),持有MySQL会话锁定,MySQL可能会使表保持锁定状态(特别是因为我可以很容易地设想会导致这种情况发生的代码)。
老实说,我最熟悉一个非常基本的C扩展,我可以看到POSIX正在调用的确切内容以及确保我想要的确切行为的params ...但我不期待写作那段代码。
任何人都有与他们想分享的PHP有关的并发相关最佳实践吗?
答案 0 :(得分:9)
事实上,无论解决方案如何,我认为不需要复杂的互斥/信号量。
在阅读了dqhendricks的回答并做了一些研究之后,我发现通过$_SESSION
提供表单密钥就是您所需要的。在PHP中,会话被锁定,session_start()
等待,直到用户会话被释放。您只需要unset()
第一个有效请求上的表单键。第二个请求必须等到第一个请求释放会话。
然而,当在(不是基于会话或源ip)的负载平衡场景中运行时,事情变得更加复杂。对于这种情况,我相信你会在这篇伟大的论文中找到一个有价值的解决方案:http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/
我通过以下演示复制了您的用例。只需将此文件放到您的网络服务器上并测试它:
<?php
session_start();
if (isset($_REQUEST['do_stuff'])) {
// do stuff
if ($_REQUEST['uniquehash'] == $_SESSION['uniquehash']) {
echo "valid, doing stuff now ... "; flush();
// delete formkey from session
unset($_SESSION['uniquehash']);
// release session early - after committing the session data is read-only
session_write_close();
sleep(20);
echo "stuff done!";
}
else {
echo "nope, {$_REQUEST['uniquehash']} is invalid.";
}
}
else {
// show form with formkey
$_SESSION['uniquehash'] = md5("foo".microtime().rand(1,999999));
?>
<html>
<head><title>session race condition example</title></head>
<body>
<form method="POST">
<input type="hidden" name="PHPSESSID" value="<?=session_id()?>">
<input type="text" name="uniquehash"
value="<?= $_SESSION['uniquehash'] ?>">
<input type="submit" name="do_stuff" value="Do stuff!">
</form>
</body>
</html>
<?php } ?>
答案 1 :(得分:1)
您可以在会话数据中的数组中存储随机哈希,并将该哈希打印为隐藏表单输入值。当请求进入时,如果会话数组中存在隐藏的哈希值,则可以从会话中删除哈希并处理表单,否则不会。
这可以防止重复提交表单以及帮助防止csrf攻击。
答案 2 :(得分:1)
您有一个有趣的问题,但您没有任何数据或代码可供展示。
对于80%的情况,如果您遵循关于阻止用户多次提交表单的标准程序和做法,那么由于PHP本身发生任何令人讨厌的事情的几率几乎为零,这几乎适用于所有其他设置,而不仅仅是PHP。
如果你是20%并且你的环境需要它,那么一个选项就是使用我确定你熟悉的消息队列。同样,这个想法与语言无关。与语言无关。关于数据如何流动的全部内容。
答案 3 :(得分:0)
如果问题只出现在相隔几毫秒的按钮上,那么软件去抖动器是否有效?就像保存按钮的时间按会话变量并且不再允许,例如,一秒钟?只是我早上喝咖啡的想法。欢呼声。
答案 4 :(得分:0)
我为了防止代码中的会话竞争条件而做的是在会话中存储数据的最后一个操作之后我使用PHP函数session_write_close()注意如果你使用PHP 7则需要禁用默认输出缓冲在php.ini中。如果您有耗时的操作,最好在调用session_write_close()之后执行它们。
我希望它会帮助某人,对我而言,它拯救了我的生命:)