故障现象:
用户从浏览器页面发起数据请求,提交请求后约 60s 后返回 504 Gateway Time-out 。错误页面如下图所示:

其他信息:
用户是在 web 系统中按照筛选条件向 es 集群提交数据请求,es 生成Excel 表格后返回。
一次性请求的数据量大约是 3-5W 条。

分析&解决:
返回 504 Gateway Time-out,这个提示已经很明显。一般是由于后端程序(这里的后端程序一般指位于 Nginx 后面真正处理用户请求的应用程序)执行时间过长导致响应超时。不一定是真正的程序故障。例如程序需要执行 90 秒,而 nginx 只等待了 60 秒(60秒是 Nginx 默认等待时间),这样就会出现超时。
常见的超时通常有以下几种情况:
后端程序要处理的数据量较大或者数据生成时间较长,没有及时返回结果给 Nginx,导致超时。
后端程序中调用外部请求或者其他服务,而外部请求响应超时或者其他服务响应超时。例如内网调用公网域名。
连接数据库失败而没有停止,发生死循环重新连。
出现超时,可以考虑优化后端程序或者调用链,缩短执行时间。另一方面,还可以考虑调大 nginx 超时限制的参数,使程序可以正常执行。
从本案例来看,请求发起60s之后页面504,而不是很快就返回504。并且现象能够稳定复现。基本符合上述猜测。结合用户的描述,该请求是请求大量数据,因此处理思路匹配上述第一条,应该先从Nginx的各种超时配置考虑。
nginx超时配置很多,主要有以下几个:
http {#读取http头部的超时时间,单位秒,连接建立后,服务端接收http头部,规定时间内没收到,则超时,返回给客服端408(request time out)client_header_timeout 60;#读取http body的超时时间,单位秒,连接建立后,服务端接收body,规定时间内没收到,则超时,返回给客服端408(request time out)client_body_timeout 60;#发送响应超时时间,单位秒,服务端向客户端发送数据包,规定时间内客户端没收到,则超时send_timeout 60;#保持闲置连接的超时时间,单位秒,超过后服务器和浏览器都会关闭连接keepalive_timeout 75;#域名解析超时时间,单位秒resolve_timeout 30;#nginx服务器与被代理服务连接超时时间,代理超时proxy_connect_timeout 60;#nginx服务器发送数据给被代理服务器超时时间,单位秒,规定时间内nginx服务器没发送数据,则超时proxy_send_timeout 60;#nginx服务器接收被代理服务器数据超时时间,单位秒,规定时间内nginx服务器没收到数据,则超时proxy_read_timeout 60;}
如果不是很清楚这些超时的使用场景,最笨的解决办法方法就是按个尝试以上超时设置,但这个方法并不可取。

盲目地反复reload nginx集群是不能接受的。接下来需要判断本次是符合哪个超时配置。

经过跟用户沟通确认和查看nginx配置得知,该url请求先经nginx转给后端java程序,然后java程序直接通过es集群的ip而不是域名)继续向后发起请求。
于是让用户做一步验证:
将上述返回504的域名与用户的后端java程序的ip做本地hosts绑定。经验证,该方式能正常返回数据。但返回数据需要等待约5分钟。说明java程序和es集群运行正常,都是没问题的。但此时也不考虑nginx程序存在故障,因为nginx集群上的其他http业务和域名都正常。并且没有做变更的前提下,Nginx运行稳定通常不会出现问题。这样nginx、java、es程序本身的问题和网络的问题,就先排除了。
还有个重点信息:
请求经由nginx转发给java,此时是 http 请求。再从java转发给es时是通过ip而不是域名,此时是tcp请求。
刚才验证了java程序正常,所以不考虑nginx到java之间存在异常,先排除client_header_timeout和client_body_timeout这两个http的参数。
用户绑定hosts验证OK,排除send_timeout。keepalive_timeout不符合场景,也排除。
如果是解析resolve_timeout问题,很容易验出来,也排除。
现在只剩下proxy的三个参数。综合前面的各种分析和验证,判断是es处理数据时间较长,nginx没接收到来自es端返回的数据,于是按照默认的超时时间(即60s)直接把504返回给了客户端。所以最关键的参数应该是dproxy_read_timeout。刚才绑定hosts请求需要5分钟,于是更改为600s。
再次请求,成功返回数据!!!

这里其实还排除了另一种猜测:
即怀疑几万条的数据生成 Excel 后文件较大,导致返回时间超过了60s所以返回504。如果是这种情况,应该将参数 client_max_body_size 和proxy_max_temp_file_size (nginx 下载文件大小的限制)的值调大,即允许客户端下载大文件。这种猜测其实很容易否定,原因是,当已经确定网络和各节点程序(nginx、java、es)都正常的情况下,如果是由于 Excel 过大导致的问题,返回浏览器的就不应该是 504 。后来的事实证明,几万条数据的Excel不到1MB 。
总结:
本次故障处理,虽然更多是依靠经验以及对生产环境的了解,所以得以快速地判断和解决。但是当我们遇到没处理过的新问题时,最重要的是要有个清晰的逻辑和思路。先充分搜集有用信息,然后做出基本的判断,最后依次进行尝试。这才是一位合格的技术人员应该具备的素质。





