PHP中长时间运行的脚本会导致NGINX服务器变得非常繁忙


Long running-time script in PHP causes NGINX server to get very busy

我会尽量具体说明这一点——这并不容易,所以请尽量遵循。

我们有一个在NGINX上用PHP运行的脚本-PHP fpm FastCGI。这个脚本从试图访问它的用户那里获取信息,并在上实时运行一些算法。它不能是在后台运行的计划进程。有时,页面加载甚至需要5-12秒,还可以。通常,我们从用户那里收集数据,并向第三方服务器发出几个传出请求,收集数据,分析数据,并为用户返回响应。

问题是,有许多用户在运行此脚本,服务器变得非常繁忙,因为他们都是服务器上的活动连接,正在等待响应。我们有两台服务器在一个负载均衡器下运行,这还不够。有时服务器一次有1500个以上的活动连接。你可以想象这些服务器在那个时间段是如何响应的。

我正在寻找解决方案。我们可以向LB添加越来越多的服务器,但这是唯一的解决方案,这听起来很荒谬。我们仔细研究了这个脚本,并最大限度地优化了它,我可以向你保证——对于该脚本的长期运行,没有真正的解决方案,因为它依赖于第三方服务器,这些服务器需要时间才能在实时流量上对我们做出响应。

你能想出一个解决方案吗?保持这个脚本的原样-但是以某种方式降低这些活动连接对整个服务器功能的影响?有时,他们只是简单地停下来回应。

非常感谢您的阅读!

3个月大的问题,我知道,但我忍不住想:

  1. 如果您确信所有向第三方服务器发出的请求的网络工作量加上PHP脚本内相应的响应处理的总和远低于硬件的限制。

  2. 然后,您的PHP脚本可能会低效地忙于循环,直到所有响应都从第三方服务器返回

如果我在处理这样一个问题,我会这么做:

  1. 停止使用自定义的外部C++curl,因为PHP脚本正忙于等待它。

  2. 谷歌和阅读PHP的curl多实现的非繁忙循环使用

希望这是有道理的。

我的建议是为请求设置有限的超时,并对每个第三方请求使用异步请求

例如,对于您的页面,您必须显示5个第三方请求的结果。这意味着,在脚本内部,您调用cURLfile_get_contents 5次,但每次来自第三方的超时,脚本都会被冻结。循序渐进。这意味着,如果每个请求都需要等待10秒才能得到响应,那么总共有50秒的时间
User calls the script -> script wait to end -> server is loaded for 50 seconds

现在,如果每个到第三方的请求都将异步发送,这将把脚本的加载时间减少到最大请求延迟。因此,您将有一些较小的脚本,它们的使用寿命会更短,并且会减少服务器上的负载
User calls the script -> script is loaded -> requests are sent -> there are no scripts that are waiting for the response and consuming resources of your server

愿AJAX与您同在!)

这是一个非常古老的问题,但由于我遇到了类似的问题,我可以分享我的解决方案。长时间运行的脚本会影响系统的各个部分,并在Web服务器(在活动连接中)、php-fpm和mysql/其他数据库中造成压力。这些往往会导致一些连锁效应,例如其他请求开始失败。

首先确保你有网络数据(https://github.com/netdata/netdata)安装在服务器上。如果你正在运行许多实例,你可能会发现拥有Grafana/Prometheus设置也是值得的。

接下来,确保它可以看到PHP FPM进程、Mysql和Nginx。Netdata显示了很多东西,但对于这个问题,我的关键指标是:

  • Connections(mysql_local.Connections)-是充满连接的数据库
  • PHP-FPM活动连接(phpfpm_local.Connections)-PHP是否无法跟上
  • PHP-FPM请求持续时间(phpfpm_local.Request_Duration)-处理的时间到了吗
  • 磁盘利用率时间(Disk_util.sda)-这显示磁盘是否无法保持(100%=负载不足)
  • 用户打开文件(Users.Files)

确保您有足够的文件句柄(https://www.cyberciti.biz/faq/linux-increase-the-maximum-number-of-open-files/),并且磁盘未被完全占用。这两者都会阻止你让东西发挥作用,所以在麻烦的服务器上把它们做大。

下一步检查Nginx在Nginx.conf中是否有足够的资源:

worker_processes auto;
worker_rlimit_nofile 30000;
events {
    worker_connections 768;
}

这会让你有时间找出问题所在。

接下来看php-fpm(/etc/php/7.2/fpm/pool.d/www.conf):

  • 将pm.max_spare_servers设置为高,例如100
  • 设置pm.max_requests=500,以防脚本无法正常释放

然后看。对我来说,问题是每个请求都会阻止一个传入连接。对同一脚本的更多请求将阻止更多连接。机器可能运行良好,但一个慢速脚本执行curl命中或一个慢速SQL语句将在其整个持续时间内占用该连接,因此30秒=处理传入请求的php进程减少1个。最终你达到了500,然后就跑完了。如果可以增加FPM进程的数量,使慢速脚本请求的频率与它们运行的秒数相匹配。因此,如果脚本需要2秒,并且每秒被命中2次,那么您将需要一个恒定的4个额外的fpm工作线程,它们什么都不做。

如果你能做到这一点,那就到此为止-除此之外的额外努力可能不值得。如果仍然觉得这会是一个问题,那么在盒子上创建第二个php-fpm实例,并将对慢脚本的所有请求发送到该新实例。这允许您在运行时间过长的情况下离散地使这些请求失败。这将使你有能力做两件重要的事情:

  • 控制用于慢速脚本的资源量
  • 意味着所有其他脚本永远不会被慢速脚本阻止,并且(假设操作系统限制足够高)永远不会受到资源限制的影响

希望这能帮助在负载下挣扎的人!