防止PHP中竞争条件的最可靠和安全的方法

Mah*_*dsi 22 php concurrency

我需要在PHP中使用互斥锁或信号量,这让我很害怕.为了澄清,我并不害怕编写无死锁的代码,这些代码可以正常同步或者担心并发编程的危险,但是PHP处理边缘情况的程度如何.

快速背景:编写位于用户和第三方信用卡网关之间的信用卡处理程序界面.需要防止重复请求,并且已经有一个适用的系统,但是如果用户点击提交(w/out JS启用,所以我无法为他们禁用按钮)相隔毫秒,一个竞争条件接着我的PHP脚本没有意识到已经发出了重复的请求.需要一个信号量/互斥量,这样我才能确保每个唯一的事务只有一个成功的请求.

我通过PHP-FPM在多核Linux机器上运行多个进程,在nginx后面运行PHP.我想确定一下

  1. 所有php-fpm进程和所有内核(i686内核)之间共享信号量.
  2. php-fpm在持有互斥锁/信号量时处理PHP进程崩溃并相应地释放它.
  3. php-fpm在持有互斥锁/信号量时处理会话中止并相应地释放它.

是的我知道.非常基本的问题,认为任何其他软件都不存在适当的解决方案是愚蠢的.但这是PHP,它肯定不会考虑并发性,它经常崩溃(取决于你加载了哪些扩展),并且处于易变的环境中(PHP-FPM和Web).

关于(1),我假设如果PHP使用POSIX函数,这些条件在SMP i686机器上都适用.至于(2),我从简要略读文档看到有一个参数决定了这种行为(虽然为什么人们希望PHP不释放互斥锁是会话被杀死我不明白).但是(3)是我的主要关注点,我不知道是否可以安全地假设php-fpm正确地为我处理所有边缘情况.我(显然)不想要死锁,但我不确定我是否可以信任PHP,永远不会让我的代码处于无法获取互斥锁的状态,因为抓住它的会话要么优雅地还是不合理地终止.

我考虑使用MySQL LOCK TABLES方法,但更令人怀疑是因为虽然我相信MySQL锁定比PHP锁定更多,但我担心如果PHP在持有MySQL会话锁定时中止请求(带*out*崩溃),MySQL可能会保持表格锁定(特别是因为我可以很容易地设想会导致这种情况发生的代码).

老实说,我最熟悉一个非常基本的C扩展,在那里我可以确切地看到POSIX正在调用什么以及使用什么params来确保我想要的确切行为..但我不期待编写该代码.

任何人都有关于他们想要分享的PHP的并发相关最佳实践吗?

Kai*_*aii 11

事实上,我认为没有必要使用复杂的互斥/信号量解决方案.

在阅读了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 } ?>
Run Code Online (Sandbox Code Playgroud)

  • 注意,如果您正在使用文件系统上的默认实现,会话仅被锁定*.如果您正在使用自定义会话处理程序,那么实现锁定是有责任的.老实说,"羊群"没有任何错误. (3认同)