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

2022-08-30 22:22:25

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

我很担心,因为我认为它可能会导致服务器崩溃,因为未关闭的请求负载很高。是的,我试过了,但没有改变。我有可能应该在Nginx中改变一些东西吗?ignore_user_abort(false);

  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;  
  }

答案 1

坏消息是,你几乎肯定无法解决你想解决的问题。客户端在接收请求之前关闭连接时发送的 FastCGI 信号FCGI_ABORT_REQUEST

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

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

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

  • 将其推送到需要处理的任务队列。
  • 将“任务 ID”返回给客户端。
  • 让客户端定期轮询该“任务”是否已完成,并在完成后显示结果。

除了这3件基本的事情 - 如果你担心当客户端不再对请求的结果感兴趣时浪费系统资源,你应该添加:

  • 将任务分解为小块工作,并且如果客户端仍在请求结果,则仅将任务从一个工作“状态”移动到下一个工作“状态”。

您不会说出长时间运行的任务是什么 - 让我们假装它是从另一台服务器下载一个大型图像文件,操作该图像,然后将其存储在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

因此,当客户端发送初始请求时,它会返回一个 taskID,然后在查询该任务的状态时,执行以下操作之一:

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

  • 如果它处于以下状态之一,则客户端请求会将其提升到下一个状态。

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

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

顺便说一句,我强烈建议使用一些旨在高性能工作的东西作为队列来保存任务队列(例如RabbtimqRedisGearman),而不仅仅是使用MySQL或任何数据库。基本上,SQL并不擅长充当队列,您最好从一开始就使用适当的技术,而不是使用错误的技术来启动,然后在数据库过载时必须将其交换出来,当它试图执行数百次插入时,每秒更新只是为了管理任务。

作为附带的好处,通过将长时间运行的流程分解为任务,可以很容易地:

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

答案 2

在这些长时间运行的请求中,您究竟在做什么?如果您正在执行的任何操作都导致 FastCGI 进程等待某些系统调用(如等待数据库返回结果),则中止的 HTTP 客户端连接不会导致此调用中断。如果我没记错的话,其效果仅仅是PHP脚本在尝试将某些内容输出到(现在丢失的)连接时立即中止。脚本在等待系统调用时不会写入任何输出。ignore_user_abort(false)

如果可能,应将长时间运行的脚本正在执行的任务拆分为较小的块,并在处理它们之间检查连接状态。如果连接终止,请确保脚本终止:

while (!$done_yet) {
    if(connection_status() != CONNECTION_NORMAL) {
        break;
    }
    do_more_work();
}

在 PHP 文档中,如果您愿意,可以找到有关连接处理的更多信息。


推荐