关于Nginx499、502和504的分析
我相信有些人在面试运维类岗位的时候会碰到对方问关于这方面的问题,我这里通过几个实验来复现这个情况,并做出相关分析,我希望大家看完后针对这种问题能有一个清晰思路。
服务器 | IP |
---|---|
Nginx | 192.168.10.40 |
后端Web | 192.168.10.50 |
我们使用一个flask制作一个小的后端程序,程序里加了sleep,为的是有时间你可以进行其他操作,比如终止进程:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask
import random
import time
app = Flask(__name__)
@app.route('/')
def hello_world():
time.sleep(10)
html = "Hello World!"
return html
if __name__ == '__main__':
app.run(host="0.0.0.0", port="5555")
下面是Nginx配置:
server {
listen 80;
server_name www.test.com;
#charset koi8-r;
access_log /var/log/nginx/www.test.com/access.log detailed;
error_log /var/log/nginx/www.test.com/error.log error;
location / {
proxy_pass http://192.168.10.50:5555;
proxy_set_header host www.test.com;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# proxy_ignore_client_abort on;
}
#error_page 404 /404.html;
#要保证下面的root目录中有50x.html,否则你只会收到404的代码
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
Nginx使用的日志格式
log_format detailed '$remote_addr - $remote_user [$time_local] '
'$server_protocol $scheme $ssl_protocol $http_host $request_method "$content_type" "$request" '
'$status $body_bytes_sent "$http_referer" $request_time '
'"$http_user_agent" "$http_x_forwarded_for" '
'[$upstream_addr] [$upstream_response_time] [$upstream_status] [$upstream_response_length]';
实验1:状态码499
其实499很好模拟,运行命令启动flask:
python3 ./myweb.py
确保程序已经开始监听
打开浏览器访问www.test.com由于后端10秒返回(Python代码中有一个sleep函数)数据,所以当你访问这个URL之后,马上按终止或者关闭浏览器。
这时候Nginx的日志就是499
这个就是客户端在服务器还没有返回数据之前就终止了,也就是不再等待了。对于浏览器来说可能就是用户主动终止了。另外还有一种情况就是远程调用,对端设置的超时时长如果是5秒,那么由于我们程序设置的10秒后返回,对端到达5秒后会中断连接,那么我们的Nginx上就会产生499,其实这个和终止浏览器是一个意思。
proxy_ignore_client_abort on
另外网上有说加上这个参数就没有499了,其实这个参数是忽略了客户端终止连接的这种情况,所以它在日志中就没有499了。
实验2:状态码502(Bad Gateway)
情况一:
使用上面的配置不做任何改变。然后运行命令:
python3 ./myweb.py
打开浏览器访问www.test.com,由于处理程序我设置的是10秒,所以我们有时间做其他操作,当请求进来时程序所在终端会显示如下信息:
在还没有返回的时候你直接按下CTRL+C来终止Flask程序,Nginx访问日志马上就是502错误。
查看Nginx访问日志和错误日志:
下面是抓包情况,最后一个箭头,我终止了flask程序,发送了fin给对方,所以Nginx的错误日志显示permaturely closed connection。
这种情况下后端处理程序因为某些原因被终止,导致Nginx到后端程序这条socket连接断开那么就会报这个错误。但是从抓包可以看到四次断开是正常完成的,也就是说你按CTRL+C或者kill -9 PID 这个程序,它内部还是会正常断开连接,对于Nginx来说相对于在等待数据返回的时候却收到了一个FIN。
情况二:
修改一下Nginx的配置:
server {
......
location / {
proxy_pass http://192.168.10.50:5556; # 修改一个不存在的端口
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
......
}
我们这里把后端指向了一个后端服务器不存在的端口,然后通过浏览器访问,你会马上得到502状态码,错误日志信息为“connect() failed (111: Connection refused)”。
情况三:
这种情况不好模拟,这时候就不能用之前的flask框架的程序了,需要自己写一个套接字程序,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import os, time, sys
import signal
def echoStr(connFd, sleep_time):
print("新连接:", connFd.getpeername())
sleep_time = sleep_time
print("睡眠 %s 秒后返回数据" % sleep_time)
time.sleep(sleep_time)
try:
with open("./index.html", "r") as f1:
html_data = f1.read()
connFd.send(html_data.encode(encoding="utf-8"))
except Exception as err:
print(err)
def main():
sockFd = socket.socket()
sockFd.bind(("", 5555))
sockFd.listen(5)
signal.signal(signal.SIGCHLD, sigChld)
print("等待客户端连接......")
while True:
connFd, remAddr = sockFd.accept()
try:
# 10 是传递进去的睡眠时间
echoStr(connFd, 10)
connFd.close()
except Exception as err:
print(err)
if __name__ == '__main__':
main()
启动程序python3 ./mysocket.py
然后使用浏览器访问,依然是通过Nginx做代理,Nginx无需修改,在10秒内CTRL+C来终止程序,这时候相当于是向套接字发送RES:
查看Nginx日志:
看看抓包情况
前面部分都和情况一相同,但是你注意最后一个箭头,它是[R.]而且方向是从50到40方向,也就是后端给Nginx发的,这就是我在程序收到请求以后按了CTRL+C终止了程序,这就等于向双方建立的套接字发送了RST,当Nginx尝试去读一个已经收到RST的套接字的时候就会得到ECONNRESET错误,当然如果是写操作也会得到这个ECONNRESET。
我这里使用CTRL+C和情况一中不同,同样的操作但是对于套接字的影响不同,这是因为你在套接字编程的时候一定需要考虑进程崩溃怎么办、网络临时抖动怎么办,flask框架里面的web服务是有这些机制的,当它收到终止操作信号的时候会做哪些后续处理,我们看到它是通过正常机制来完成了四次端口,而反观我自己写的这个简易Socket程序没有任何其他考虑,收到终止信号后直接就发送了RST。
小结
针对Nginx在什么情况下产生502我们找到3种:
代理到一个不存在的端口,记住这里是端口,不是地址。 在error_log中显示 connect() failed (111: Connection refused) 访问日志出现502,
后端终止了程序,但正常完成了四次断开,error_log中显示 permaturely closed connection。
后端终止了程序,但没有完成四次端开而是发送了RST,error_log中显示 recv() failed (104: Connection reset by peer)
实验3:状态码504(Gateway Timeout)
情况一:
这个就是Nginx超时导致的,我们修改一下Nginx配置:
server {
......
location / {
proxy_pass http://192.168.10.50:5555;
proxy_set_header host www.test.com;
proxy_connect_timeout 60s;
proxy_read_timeout 5s; # 修改为5秒
proxy_send_timeout 60s;
# proxy_ignore_client_abort on;
}
......
}
我们把proxy_read_timeout改成了5秒,这样Nginx等待后端回传数据只能5秒,因为我们后端是10秒后响应。然后重新reload配置。之后启动我们的Flask程序进行访问测试:
通过浏览器访问,并观察Nginx日志
这个原因就是Nginx的proxy_read_timeout超时时长小于,后端处理时长。但其根本原因是当Nginx的read或者send到达超时时长后端还没有返回响应那么Nginx就会主动断开和后端的,也就是主动发送FIN,从而产生了504。如下图:
我这个例子是把read改为5秒,造成读超时,其实对于proxy_send_timeout也一样,如果写超时也是也是504。
情况二:
这个就需要修改的地方会多一些,我们一点一点来
修改Nginx主机的/etc/hosts文件,添加一条,如下:
192.168.10.50 www.myweb.com
修改Nginx的配置,如下:
server {
......
location / {
proxy_pass http://www.myweb.com:5555; # 改成域名
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s; # 还原为60秒
}
......
}
启动后端程序,然后用浏览器访问看看可以正常访问页面,如果愿意还可以修改Python程序中的sleep时间,改成0,我们这个测试对于后端响应时间没有要求。
这时候你把/etc/hosts中添加的那个解析记录删除,你再次访问,会发现还可以访问到页面内容,而且Nginx日志里都是正常的。
下面则是最关键的,你把运行flask程序的主机也就是192.168.10.50这个主机IP修改为60(总之就是和之前不一样):
然后再次用浏览器访问,你觉得会发生什么?肯定还是访问不到,看看日志:
你可以看到错误日志和情况一是一样的,但是场景不同了。有人说我把HOST注释了,其实你就是把解析记录修改为新的IP地址其结果也是一样的。这是因为Nginx自己有解析记录的缓存,由于我们在proxy_pass中使用的是域名,这种场景在实际上是存在的,面对这种场景你有2个办法:
reload nginx,这种办法对于域名解析变动非常不频繁的情况
使用变量,这种办法对于域名解析比较频繁的情况
如何使用变量呢,如下所示:
server {
listen 80;
server_name www.test.com;
# 新增
resolver 127.0.0.1:5353 [::1]:5353 valid=30s;
access_log /var/log/nginx/www.test.com/access.log detailed;
error_log /var/log/nginx/www.test.com/error.log error;
location / {
# 新增
set $backendname http://www.myweb.com;
# proxy_pass http://www.myweb.com:5555;
proxy_pass $backendname:5555;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
......
}
先说一下变量名引用和直接代理有什么不同:
proxy_pass http://www.myweb.com:5555
这种方式将tengine在启动的时候使用系统的域名服务器把www.myweb.com解析成ip。 如果系统的域名服务器不能解析出ip,tengine将不能启动。变量方式,tengine在运行的时候动态解析域名,也就是在访问的时候才进行解析,使用Tengine的 reslover 指令配置的域名服务器。 tengine会保存域名解析的TTL时间,在TTL时间内直接使用这个ip,过期之后重新域名解析。
两者的利弊: 第一种不会感知到域名的ip变动,启动之后就不依赖域名服务器; 第二种配置会感知到域名的ip变动,但运行时依赖域名服务器,如果域名服务器挂了会返回502,而并不会尝试使用本机配置的其他DNS服务器来解析。
注意:通过变量的形式配置如果你不resolver的话,Nginx是不会用本地的hosts或者resolv文件去解析的。
我这里就在我Nginx这个服务器上安装了一个dnsmasq,通过yum安装yum -y install dnsmasq
,编辑配置文件/etc/dnsmasq.conf
:
port=5353
listen-address=127.0.0.1
resolv-file=/etc/resolv.conf
addn-hosts=/etc/hosts
最关键的就是最后一句,因为对www.myweb.com的解析就在本地hosts文件中。配置好后启动服务。然后再次访问页面就可以了。
小结
针对504的我们模拟了2种情况,你从error日志里看其实根本原因都一样就是连接超时,只不多第一种是属于主动超时,第二种属于被动超时,但不管怎么样都是在一定时间内得不到响应,这种响应可以是连接层面的响应也可以是后端数据返回的响应,但重点不在响应本身而是在于一定时间。
总结
499无需多说,这个在Nginx代码的定义就是NGX_HTTP_CLIENT_CLOSED_REQUEST,客户端关闭请求,这就是Nginx产生499的原因,这句定义你可以解读出2个信息,第一客户端和Nginx已经建立了连接,第二在Nginx还没有完成响应动作的时候客户端关闭了连接,至于客户端为什么关闭请求那是另外一回事,这种原因可就非常多了。
502:Bad Gateway,意为网关错误,这就是说你访问的Web服务器充当的是一个网关或者代理角色(比如我们例子中的Nginx反代),当Nginx从上游服务器收到无效响应的时候会产生该状态码。最后一句“从上游服务器收到无效响应”,可以看出一切的首先是得先能找到上游服务器,如果都找不到服务器那么响应的有效与否就无从谈起,其次是无效响应。在我们的例子中,Nginx可以通过IP找到后端,在后端进行响应的时候后端发生问题,这种情况就包括端口无效或者端口有效建立了连接但是后端进程处理时候发生问题导致中断了连接。在这些情况下Nginx收到的响应都不是后端应用发给它的,都是后端服务器发给的,进一步说就是后端服务器内核中的TCP/IP协议栈发给Nginx的。所以它的关注点不在时间维度上而是无效响应这个点。
504:Gateway Timeout,意为网关超时,扮演网关和代理的服务器无法在规定时间内得到有效响应。这里你就可以看出区别,502是无效响应,504是有效响应。但504的定义中有一个时间概念,所以它关注的点是时间,如果在规定时间内得到响应,无论是有效的还是无效的,那么其状态码一定不是504,如果是有效数据则状态码是2XX、3XX或者是4XX,如果是无效数据那么就是502。
其实Nginx为什么产生499、502或者504你去看看错误日志就知道了,就可以做一个归类。面对这种问题你先不要想后端是怎么回事,大家的后端千差万别。不要被别人代跑。
关于Nginx499、502和504的分析的更多相关文章
- Nginx提示502和504错误的解决方案
一.错误提示说明: Nginx 502 Bad Gateway的含义是请求的PHP-CGI已经执行,但是由于某种原因(一般是读取资源的问题)没有执行完毕而导致PHP-CGI进程终止. Nginx 50 ...
- Nginx中502和504错误详解
在使用Nginx时,经常会碰到502 Bad Gateway和504 Gateway Time-out错误,下面以Nginx+PHP-FPM来分析下这两种常见错误的原因和解决方案. 1.502 Bad ...
- HTTP请求的502、504、499错误
1.名词解释 502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应(伪响应). 504 Gateway Time-out:作为网关或者代理工作的服务 ...
- nginx自定义500,502,504错误页面无法跳转【转】
1.自定一个页面,这个页面是一个链接地址可以直接访问的. 以下是nginx的配置: location / { proxy_pass http://tomcat_app108; ...
- 一键解决 500、502和504 Internal Privoxy Error 问题(图文详解)
最近获得一个SS帐号,手机,其他电脑都能上,但是在我的电脑上就是500 或 502 或 504,如下所示. 502 Read from server failed: Unknown error Th ...
- 解决 502、504 Gateway Time-out(nginx)
一.504 Gateway Time-out问题常见于使用nginx作为web server的服务器的网站 我遇到这个问题是在升级discuz论坛的时候遇到的 一般看来, 这种情况可能是由于nginx ...
- nginx和fpm的进程数配置和502,504错误
502 和 php-fpm.conf 1.php-cgi进程数不够用.php执行时间长,导致没有空闲进程处理新请求. 2.php-cgi进程死掉.php-fpm超时时间短,当前进程执行超时关闭连接. ...
- 502 VS 504
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/89 首先看一下概念: 502:作为网关或者代理工作的服务器尝试执 ...
- nginx+php设置大文件请求上传(502及504问题处理)
502问题 php-fpm 修改项: request_terminate_timeout 位置: eg: /etc/php5/fpm2/pool.d/www.conf ; The timeout fo ...
随机推荐
- jquery trim()的用法
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- ELK日志系统:Elasticsearch + Logstash + Kibana 搭建教程 good
环境:OS X 10.10.5 + JDK 1.8 步骤: 一.下载ELK的三大组件 Elasticsearch下载地址: https://www.elastic.co/downloads/elast ...
- 文字滚屏控件(SliderPanel)
http://www.delphifans.com/infoview/Article_629.html 日期:2005年9月6日 作者:arhaha {==================== 满天星 ...
- Win10《芒果TV》更新v3.8.50勇敢版:新增短信和扫码登录
勇敢,是心中最初的信仰,实景科幻实验节目<勇敢的世界>,重装上阵对抗升级,<中餐厅2>皇阿玛圣驾亲临,坐镇中国味道.Win10版<芒果TV>全平台同步更新勇敢版v3 ...
- 如何计算memcache的容量
在容量足够的情况下,当然是越大越好,但这样会造成浪费.不考虑这种情况.我们一般的情况是: memcache集群一开始创建会根据存储的数据量与访问量进行容量大小的估算.再算一个20%的冗余. 在网站快速 ...
- shell脚本配置ssh免密登陆
通过shell脚本配置免密登陆,分为两个脚本,一个是配置文件config.env,一个是正式脚本sshkey.sh. # config.envexport HOST_USER=(root) expor ...
- 配置QtCreator+CDB远程调试环境(用到了符号表) good
相关环境信息:开发机Win7 x64.远程机器WinXP.调试器是CDB.Qt版本5.2.1 一.部署远程机器环境 我这里用的是虚拟机(Windows XP),根据你要调试的程序选择安装不同架构的Wi ...
- How Qt Signals and Slots Work(感觉是通过Meta根据名字来调用)
Qt is well known for its signals and slots mechanism. But how does it work? In this blog post, we wi ...
- XP下安装ubuntu
一,环境说明 dell vostro 1400笔记本,winxp sp3操作系统,ubuntu-9.10-desktop-i386.iso 写这篇随笔的时候我用的已经是ubuntu了. 我是在我的移动 ...
- mac下 编译php的 openssl
编译openssl.so tar zxvf php-7.2.8.tar.gz# 进入PHP的openssl扩展模块目录cd php-7.2.8/ext/openssl/brew install ope ...