小议504 Gateway Time-out

在最近的项目中,组内小伙伴在负责跑一个批量作业的任务,数据量条数在万级别,每处理一条数据需要一定的耗时;每次在跑了小部分的时候,请求就中断了,并报504 Gateway Time-out错误,结果如下图:

504?挺简单吧?不就是因为超时引起?

不过仔细探究,超时的原因太多了,其实说简单也复杂;

了解到该项目的基本情况如下:

1.基本架构示例

2.进程处理细节

通过发起web请求触发了批量作业,然后通过遍历来循环处理的;

基于以上的背景,我们来一起看看以上的问题是怎么引起的;

请求到达nginx后,nginx调用fastcgi模块将请求转发给上游的php进程,php的进程管理器(php-fpm)会将请求分配给真正处理请求的work子进程;work子进程处理完毕后,然后一步步发送响应,最终将结果返回给客户端;

目前的结果是,客户端浏览器收到了504的超时响应;那总体来看,应该是上述请求流程的中间环节出问题,导致了最终的超时响应;

基于此架构模型,我们先一起学习下Nginx官方的资料;

nginx官网文档:

http://nginx.org/en/docs/

nginx fastcgi 模块文档:

http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

我们看到,nginx和php进程通信的时候,过程也是一样的:建立连接、转发请求、等待相应;中间有3个很关键的与timeout有关的参数:

fastcgi_connect_timeout,fastcgi_send_timeout,fastcgi_read_timeout;

下面是这三个关键的参数在nginx官网文档中的标准解读:

(1)fastcgi_connect_timeout

Doc:http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_connect_timeout

Syntax: fastcgi_connect_timeout time;

Default:

fastcgi_connect_timeout 60s;

Context: http, server, location

Defines a timeout for establishing aconnection with a FastCGI server. It should be noted that this timeout cannotusually exceed 75 seconds.

定义与FastCGI服务器建立连接的超时。应该注意的是,这个超时通常不能超过75秒。

(2)fastcgi_send_timeout

Doc:http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_send_timeout

Syntax: fastcgi_send_timeout time;

Default:

fastcgi_send_timeout 60s;

Context: http, server, location

Sets a timeout for transmitting a requestto the FastCGI server. The timeout is set only between two successive writeoperations, not for the transmission of the whole request. If the FastCGIserver does not receive anything within this time, the connection is closed.

设置将请求发送到FastCGI服务器的超时。超时只设置在两个连续的写操作之间,而不是整个请求的传输。如果FastCGI服务器在这段时间内没有收到任何信息,则连接将关闭。

(3)fastcgi_read_timeout

Doc:http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_read_timeout

Syntax: fastcgi_read_timeout time;

Default:

fastcgi_read_timeout 60s;

Context: http, server, location

Defines a timeout for reading a responsefrom the FastCGI server. The timeout is set only between two successive readoperations, not for the transmission of the whole response. If the FastCGIserver does not transmit anything within this time, the connection is closed.

定义从FastCGI服务器读取响应的超时。超时只设置在两个连续的读操作之间,而不是整个响应的传输。如果FastCGI服务器在这段时间内没有传输任何内容,连接就会关闭。

3.问题重现与剖析

下面咱用实例来一起揭晓一下timeout的神秘面纱:

结合刚才项目的问题,我们发现,其实请求已经由nginx层面到达了php进程,并且已经有部分数据被php-fpm成功处理了;只是未完全处理完毕就返回了504响应;由此我们可以推断,fastcgi_connect_timeout和fastcgi_send_timeout的时间都没有超时,很有可能是fastcgi_read_timeout超时了;fastcgi_read_timeout 的默认时间是60秒钟,带着这些推断和事实,下面我们来实际的测试并剖析问题:

先准备一个测试脚本

简单测试一下该脚本程序运行时的基本总耗时,约合61秒。

在通过web请求测试脚本之前,我们需要先把php的max_execution_time参数值由默认的30秒调大一点,比如300s,然后我们在浏览器或者控制台发送一个web请求,得到的结果如下:

ok,正如上面猜测的一样,果然出现了504超时;

由官网文档我们得知,fastcgi_read_timeout在nginx配置文件中的有效作用域是http, server, location等配置块,于是我们可以在location块中显式增加配置一下fastcgi_read_timeout的值,将其有默认值修改为62秒(即让该大于上面的timeout.php运行所需耗时),重新加载配置文件后我们再次在浏览器或者控制台发起一个web请求,相应结果如下:

果然,配置生效啦,我们看到脚本成功响应并完整执行完毕;

由此可见,是因为php程序本身的执行时间比较久,导致nginx这层在默认的60s实际内来不及等待php-fpm返回响应结果,最终导致了超时,然后web服务器给客户端返回了504的响应;

既然明白了来龙去脉,那我们的解决方案也比较清晰了:

(1)优化程序,让其执行时间尽量小于服务器conf规则的默认值;

(2)考虑进程触发机制,既然web请求的方式需要这么多环节,岂不可考虑直接略过,用cli方式直接执行php代码达到该批量任务完整执行的结果不就得了。

(3)修改服务器配置规则,让规则来包容“程序”,显然该方案不是一个科学或现实的方案,尤其是项目的生产环境中, 必须以稳定性优先,不能以图一时之方便而酿成更严重的问题,如服务终端,work宕机等等。

分析到这里,我们已经很清晰的了解到了导致504结果的整个流程;

但在上述测试前,我们有一个小动作,就是先把php配置文件中的“允许脚本的最大运行时间”修改为了更大的值,如300秒;这是因为,上述我们的脚本至少要运行60秒以上,但在php的配置文件php.ini中max_execution_time参数的值默认只有30秒,显然不够用,所以为了清晰的分析504问题,我们先修改此值,为的就是不让它影响到我们对504问题的剖析;

那么上面已经将504问题剖析完了,接下来我们来看看PHP层面的本身有哪些关键的超时控制项呢?今天主要介绍两个,一个就是刚刚提到的max_execution_time,另一个则是request_terminate_timeout;

如果我们将max_execution_time的值修改的更小会怎么样呢?分析之前,我么先来查看一下php.ini中关于max_execution_time参数的官方说明,如下:

;;;;;;;;;;;;;;;;;;;

; Resource Limits ;

;;;;;;;;;;;;;;;;;;;

; Maximum execution time of each script, in seconds

; http://php.net/max-execution-time

; Note: This directive is hardcoded to 0 for t

he CLI SAPI

max_execution_time = 30

官方说,max_execution_time控制了php脚本本身的执行时间,即每个脚本执行时被允许的最大的耗时长,单位是秒,并且提醒我们:如果是CLI模式运行的话,其值是被硬编码为0的,即表示不会超时;

额外,除了直接在php.ini中修改此配置项的值以外,我们还可以在脚本中直接调用php的一个set_time_limit方法,以此来延长或控制脚本执行时的被允许时间;

set_time_limit方法见:

http://php.net/manual/zh/function.set-time-limit.php

官网对set_time_limit方法的说明中有下面关键几行:

我们再一次的看到,官方强调max_execution_time只是影响脚本本身的执行时间,并且在给出的example中说明了sleep等操作会被忽略,经过我的测试,sleep调用的确会被忽略;那max_execution_time的真相到底是什么,让我们来探个明白;

我们首先重新写一个demo程序,运行耗时约6秒左右,新demo见下:

于是我们将php.ini中的max_execution_time参数的值配置为5秒,然后重启php-fpm;并且不用cli模式,直接使用web请求的方式来请求该demo,得到结果见下:

清晰地看到,因为脚本运行耗时超过了max_execution_time的设置,所以直接报:

Fatal error: Maximum execution time of 5 seconds exceeded in /var/www/html/timeout.php on line 5

分析到这里,关于脚本本身的执行时间的超时控制参数max_execution_time的了解,我们已经很清楚了,那接下来让我们一起来分析request_terminate_timeout 这个参数;

我们发现,request_terminate_timeout这个参数是位于php的fastcgi的www.conf配置文件中的,该文件中主要包括了web请求中php-fpm的一些参数配置,官方的说明见下:

; The timeout for serving a single request after which the worker process will

; be killed. This option should be used when the 'max_execution_time' ini option

; does not stop script execution for some reason. A value of '0' means 'off'.

; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)

; Default Value: 0

;request_terminate_timeout = 0

翻译过来就是:

设置单个请求的超时终止时间。该选项可能会对 php.ini 中设置的max_execution_time因为某些特殊原因没有终止运行的脚本有用。设置为 ‘0’ 表示 ‘Off’。可用单位:s(秒),m(分),h(小时)或者 d(天)。默认单位:s(秒)。默认值:0(关闭)。

翻译为白话不就是,如果因某种原因,max_execution_time的设置如果失效,则可考虑用request_terminate_timeout来进一步控制一个请求的超时;并且它的默认值是0,表示该项目默认是打开的;那也就是说,如果我们的程序运行耗时是6秒左右,max_execution_time如果被设置为10秒,这个时候如果request_terminate_timeout项是开启的,并且我们设置其值是5秒,那会是怎么样呢?直接看测试结果:

我们发现,服务器直接响应了502 Bad Gateway,表示请求被迫中断了。

通过以上的分析、排查、演示,结论如下:

(1).fastcgi_read_timeout 的默认值是60秒,如果是大耗时请求,很容易导致504错误;

(2).php的配置项max_execution_time表示了运行脚本执行的最大时间,默认值是30秒;如要修改此值,可以通过在php.ini中修改,也可以直接程序开始处通过调用set_time_limit函数来临时修改;

(3).php-fpm中的request_terminate_timeout 默认是关闭的,生产环境建议开启,时间设置尽量不要太短,对于一个请求来说,如果request_terminate_timeout的配置是开启的,则request_terminate_timeout的优先级大于max_execution_time,即当request_terminate_timeout参数的值有效时,请求会优先参考该值,来决定php-fpm的超时时间,该值设置的如果不合理,很容易导致502 Bad Gatway 错误响应;

4.总结与思考

综合上述,我们花时间一起验证了下504 超时,但在这里我主要想强调的是:504错误本身并不重要,重要的是我们应学会排查分析问题的方法,这样才有可能在面对大小问题时迎刃而解;除此之外,在网速飞快的今天,1秒钟在互联网或计算机的世界里面,可以干好多事情;在不久的将来,5G+普及后,网络传输速度更是惊人;30秒钟的时长,在互联网的世界里,已经算是很漫长了;所以,在项目开发中,我们首先应先梳理清楚业务流程,然后在此基础上尽量优代码;最重要的是,建议解决问题应从根本上入手,从原理上剖析;今天的分享就到此,欢迎大家吐槽~

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181126G0KVP800?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券