每次工作中碰到 5xx 的 http 状态码,都会比较头疼,又要排查了,每次都是从 nginx 的 error_log 追溯,
一直到 php_error_log,确定大概位置,翻代码…

恰巧碰到最近组内在学习 nginx 源码,打算深入研究一下,如果有可能让偶发事件成可控事件,那一定能明白
其中的原因,更快定位问题,对日后工作排查,应该会有帮助。

Gateway Time-out

504错误是(网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
服务器(不一定是 Web 服务器)正在作为一个网关或代理来完成客户(如您的浏览器或我们的 CheckUpDown 机器人)
访问所需网址的请求。 为了完成您的 HTTP 请求, 该服务器访问一个上游服务器, 但没得到及时的响应。

这是维基百科上直译过来的一段话,网关超时,可以推测下,对于我们常见的服务模型来说,就是 client 请求到 nginx
php-fpm 未在 nginx 的最大执行时间内返回。试着构造一下:

  • 首先准备环境:
  • Ubuntu 18.04 LTSnginx version: nginx/1.14.0 (Ubuntu); PHP 7.2.14-1+ubuntu18.04.1+deb.sury.org+1 (cli)
  • 修改 Nginx 中 fastcgi_read_timeout = 3,这个参数是说从 fastcgi 读取数据的最大时间,单位为秒
    nginx.conf
    nginx.conf
  • php 代码
    index.php
    index.php
    重启 nginx,直接在浏览器请求;

和推测的完全吻合,通俗点说,就是 php 在 nginx 超时时间(设置的3s)内未返回,nginx 等不及,就会返回给客户端 504,
error_log 如下,
2018/11/28s 00:13:05 [error] 3540#3540: *3 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 192.168.10.1, server: mine.test, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock", host: "mine.test"

Bad Gateway

502状态码是服务器(不一定是Web服务器)作为网关或代理,以满足客户的要求来访问所请求的URL 。此服务器收到无效响应从上游服务器访问履行它的要求。

无效响应 可以理解是下游服务(php-fpm)不可用

还是刚才的环境,这次,停掉 php-fpm 进程 (因为我是多版本共存的,nginx配置的是php7.2的php-fpm,剩下的都是7.1php-fpm的worker进程)

关闭php-fpm服务
关闭php-fpm服务

现在,访问刚才的url

我们再模拟一种情况:在 php-fpm.conf 中有个参数设置

; 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 = 15

每个 php-fpm 的 worker 进程如果执行时间超过,会被 kill 掉。就是 php-fpm 等待程序的最大时长,
修改为 request_terminate_timeout = 3,然后重启 php-fpm,刷新浏览器。
发现和刚才的情况一样,也是 nginx 返回给 clint 一个 502 的错误页面。

在 nginx 的 error_log 里会出现这样一条日志:
2018/11/28 00:20:43 [error] 3793#3793: *20 connect() to unix:/var/run/php/php7.2-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 192.168.10.1, server: mine.test, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "mine.test"

同时,access_log 里也会增加一条日志:
192.168.10.1 - - [28/Nov/2018:00:20:43 +0800] "GET / HTTP/1.1" 504 594 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"

这两种情况,有个共同点:就是没有正常的返回给 nginx (不论是php-fpm服务不可用还是php-fpm等待超时,主动断开)。

Client has closed connection

499:客户端关闭连接 这个乍一看,是客户端的锅,是它先“动手”,关闭连接的。从标准的 RFC2616 协议中,是没有 499 状态码,
是 nginx 自己定义的。

根据字面的意思,这种情况是客户端主动断开连接,或许是服务端在客户端最大等待时间内,没有返回结果,导致客户端等不及。
这次我们在终端试试(在写的时候,已用浏览器测试,奈何浏览器作为客户端,等待时间太长了,复现不了)。

服务端还是这行代码:

<?php

sleep(10);

echo "hello world";

在终端执行,紧接着 command + c 强制终止进程

$ curl http://mine.test

从 nginx 的 access_log 中查看日志发现:
192.168.10.1 - - [28/Nov/2018:14:42:26 +0800] "GET / HTTP/1.1" 499 0 "-" "curl/7.54.0"

因为客户端主动断开,在下游的 nginx 就会有一条 499 的日志。在普通的 web 应用中很少见,但是
在微服务下,跨部门跨组之间的调用,都是 http 请求,如果下游的服务不够稳定,这种状态码一般很多。
上游要保证,在连接等待的时候,必须有合理的超时时间,不能因为下游服务挂掉了,而拖垮自己,导致
整个服务雪崩。

说到底,这三种状态码(不考虑php-fpm服务不可用时的502),都是 nginxphp-fpmclient 中的
两者之间发生超时,只要能够分清是谁timeout,就能准确的判断会发生的情况。