Nginx + PHP: 取消请求时停止进程

18

我有Nginx 1.4.4和PHP 5.5.6。我正在进行长轮询请求。问题是,如果我取消通过Ajax发送的HTTP请求,则请求仍在处理(它们不会停止)。我在文件末尾使用PHP mail()函数进行了测试,并且邮件仍在发送,文件没有停止)。

我很担心,因为我认为这可能会导致服务器崩溃,因为未关闭的请求负载太高。是的,我尝试过ignore_user_abort(false);,但没有任何变化。可能需要在Nginx中更改���些东西吗?

  location ~ \.php$ {    
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  
  }

4
每十年一次,我们会遇到一个有关于PHP的好问题。欢迎,威武的被选之人! - Madara's Ghost
2
不要误会:我喜欢PHP。但不幸的是,PHP Web SAPI并不适用于这种长轮询,因为每个并发请求都需要自己的进程。你真的在尝试把方块塞进圆孔。如果你想要一个交互式应用程序,你需要研究不同的技术。你可以使用ratchet在PHP中编写WebSocket应用程序。更强大的解决方案是咬紧牙关学习像node.js这样专门设计处理高并发客户端/请求量的技术。 - user895378
作为一种解决方法,您可以尝试定期“刷新”一些输出,例如 $buffersize=256; echo str_repeat(" ", $buffersize); flush(); 请参见 issue#115 或者使用 echoob_implicit_flush - pce
你是通过 PHP 页面上的 JavaScript 启动 Ajax 连接,即使用 jQuery 或类似方式进行 Ajax 调用吗?你的轮询设置如何?你是使用 setTimeout 还是 setInterval? - JC.
1
你能分享一下你正在使用的代码吗? - Ja͢ck
我不确定,但是在想是否拥有一个控制标记会有所帮助:PHP脚本在关闭/停止页面后继续运行 - Uours
2个回答

16

不幸的是,您几乎肯定无法以所希望的方式解决问题。当客户端在接收请求之前关闭连接时发送的FastCGI信号为FCGI_ABORT_REQUEST

当HTTP客户端在代表该客户端运行的FastCGI请求正在运行时关闭其传输连接时,Web服务器会中止FastCGI请求。这种情况可能看起来不太可能发生;大多数FastCGI请求将具有短的响应时间,如果客户端较慢,则Web服务器会提供输出缓冲区。但是,FastCGI应用程序可能会延迟与另一系统通信或执行服务器推送。

不幸的是,看起来原始的FastCGI实现PHP-FPM都不支持FCGI_ABORT_REQUEST信号,因此无法被中断。

好消息是有更好的解决方法。基本上,您永远不应该处理需要很长时间才能完成的请求。相反,如果请求需要很长时间来处理,则应:

  • 将其推入需要处理的任务队列中。
  • 向客户端返回“任务ID”。
  • 让客户端定期轮询以查看是否已完成该“任务”,并在完成时显示结果。

除了这三个基本事项之外,如果您担心在客户端不再对请求结果感兴趣时浪费系统资源,还应添加:

  • 将任务分解为小的工作单元,仅在客户端仍在请求结果时将任务从一个工作“状态”移动到下一个工作“状态”。

您没有说明长时间运行的任务是什么 - 让我们假设它是从另一台服务器下载大型图像文件,操纵该图像,然后将其存储在S3中。因此,此任务的状态将类似于:

TASK_STATE_QUEUED
TASK_STATE_DOWNLOADING //Moves to next state when finished download
TASK_STATE_DOWNLOADED
TASK_STATE_PROCESSING  //Moves to next state when processing finished
TASK_STATE_PROCESSED
TASK_STATE_UPLOADING_TO_S3 //Moves to next state when uploaded
TASK_STATE_FINISHED

因此,当客户端发送初始请求时,它会收到一个任务ID,并且当查询该任务的状态时,可能:

  • 服务器报告任务仍在处理中

或者

  • 如果它处于以下某种状态之一,则客户端请求将其推进到下一个状态。

TASK_STATE_QUEUED => TASK_STATE_DOWNLOADING
TASK_STATE_DOWNLOADED => TASK_STATE_PROCESSING
TASK_STATE_PROCESSED => TASK_STATE_UPLOADING_TO_S3

因此,只有客户端感兴趣的请求才会继续处理。

顺便说一句,我强烈建议使用专门设计用于高效工作的队列来存储任务队列(例如RabbitmqRedisGearman),而不仅仅是使用MySQL或任何数据库。基本上,SQL在充当队列时并不是那么出色,您最好从一开始就使用适当的技术,而不是从错误的技术开始,然后在数据库在试图进行数百次插入和更新每秒以管理任务时超载时,在紧急情况下进行更换。

作为附带好处,通过将长时间运行的过程分解成任务,可以轻松地实现以下目标:

  1. 查看处理时间花费在哪些步骤上。
  2. 查看和检测处理时间的波动(例如,如果CPU达到100%利用率,则图像调整大小将突然需要更长的时间)。
  3. 向速度慢的步骤投入更多资源。
  4. 您可以向客户端提供状态更新消息,以便他们可以看到任务的进度,这比它只是“无所事事”更好的用户体验。

非常感谢您的优秀表现! - l-x
太棒了!但是仅适用于您对代码具有控制权的情况。如果没有,请使用以下方法杀死孤立的php-fpm进程:https://dev59.com/t2PVa4cB1Zd3GeqP65PH#25251852 - Willem
Apache bug #56188 涵盖了 mod_proxy_fcgiFCGI_ABORT_REQUEST 的支持。 - bishop

2
你在长时间运行的请求中具体在做什么?如果你正在做的事情导致FastCGI进程等待某个系统调用,比如等待数据库返回结果,那么中止的HTTP客户端连接不会导致该调用被中断。如果我没记错的话,ignore_user_abort(false) 的效果只是当PHP脚本尝试向(现在丢失的)连接输出内容时立即中止脚本。脚本在等待系统调用期间不会写任何输出。
如果可能的话,你应该将长时间运行的脚本执行任务分成更小的块,并在处理它们之间检查连接状态。确保脚本在连接终止时终止:
while (!$done_yet) {
    if(connection_status() != CONNECTION_NORMAL) {
        break;
    }
    do_more_work();
}

在PHP文档中,如果您需要了解更多关于连接处理的信息,请查阅。

我尝试过这个方法,但它对于ajax http请求无效,你能帮我找到其他的解决方案吗? - Bharath Kumar J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接