写点什么

记一次生产环境大面积 404 问题!

用户头像
冰河
关注
发布于: 2021 年 03 月 29 日
记一次生产环境大面积404问题!

写在前面


发布到线上的接口服务一直好端端的,今天突然运营反馈说很多功能无法正常使用。经过排查,发现前端调用后端接口时,部分接口出现 404 的现象。今天,我到公司比较晚,肯定是哪个小伙伴昨晚下班,走出办公室前没有祈祷服务器不要出问题。要把这个人揪出来,吊在服务器上——祭天!


文章已收录到:


https://github.com/sunshinelyz/technology-binghe


https://gitee.com/binghe001/technology-binghe


问题复现


得知运营的反馈后,我迅速登录服务器排查问题。首先,查看了接口服务的启动进程正常。验证接口服务的 ip 和端口是否正常,结果也是没啥问题。接下来,通过 Nginx 转发请求,此时出现了问题,无法访问接口。同时 Nginx 的 access.log 文件中输出了如下日志信息。


192.168.175.120 - - [26/Feb/2021:21:34:21 +0800] "GET /third/system/base/thirdapp/get_detail HTTP/1.1" 404 0 "http://192.168.175.100/api/index.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"192.168.175.120 - - [26/Feb/2021:21:34:22 +0800] "GET /third/system/base/thirdapp/get_detail HTTP/1.1" 404 0 "http://192.168.175.100/api/index.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"192.168.175.120 - - [26/Feb/2021:21:34:26 +0800] "GET /third/system/base/thirdapp/get_detail HTTP/1.1" 404 0 "http://192.168.175.100/api/index.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"
复制代码


此时,从 Nginx 日志中发现,输出的状态为 404,未找到后端的接口服务。为了进一步定位问题,我直接在线上环境通过curl命令的方式来访问接口服务,结果是正常的。


经过这一系列的操作之后,我们就可以确定问题是出在 Nginx 上了。


问题分析


Nginx 开启 debug 模块


既然已经定位到问题了,那我们接下来就要分析下产生问题的具体原因了。既然是 Nginx 的问题,我第一时间想到的就是调试 Nginx 查找错误原因。于是我在服务器命令行输入了如下命令来查看安装 Nginx 时的配置情况。


nginx -V
复制代码


注意:这里已经为 Nginx 配置了系统环境变量,如果没有配置系统环境变量,则需要输入 nginx 命令所在目录的完整路径,例如:


/usr/local/nginx/sbin/nginx -v
复制代码


命令行输出了如下信息。


configure arguments: --prefix=/usr/local/nginx --with-http_stub_status_module --add-module=/usr/local/src/fastdfs/fastdfs-nginx-module-1.22/src --with-openssl=/usr/local/src/openssl-1.0.2s --with-pcre=/usr/local/src/pcre-8.43 --with-zlib=/usr/local/src/zlib-1.2.11 --with-http_ssl_module
复制代码


可以看到,安装 Nginx 时没有配置 Nginx 的 debug 模块。


于是我在服务器上找到了 Nginx 的安装文件,在命令行输入如下命令重新编译 Nginx。


cd /usr/local/src/nginx/  #进入Nginx的安装文件根目录make clean                #清除编译信息./configuration --prefix=/usr/local/nginx-1.17.8 --with-http_stub_status_module --add-module=/usr/local/src/fastdfs/fastdfs-nginx-module-1.22/src --with-openssl=/usr/local/src/openssl-1.0.2s --with-pcre=/usr/local/src/pcre-8.43 --with-zlib=/usr/local/src/zlib-1.2.11 --with-http_ssl_module --with-debug  #设置编译Nginx的配置信息make     #编译Nginx,切记不要输入make install
复制代码


上述命令中,切记不要输入make install 进行安装。


执行完 make 命令后,会在当前目录的 objs 目录下生成 nginx 命令,此时我们需要先停止 Nginx 服务,备份/usr/local/nginx/sbin/目录下的 nginx 命令,然后将 objs 目录下的 nginx 命令复制到/usr/local/nginx/sbin/目录下,然后启动 Nginx 服务。


nginx_service.sh stop   #通过脚本停止Nginx服务mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak #备份原有nginx命令cp ./objs/nginx /usr/local/nginx/sbin/nginx #复制nginx命令nginx_service.sh start #通过脚本启动Nginx服务
复制代码


注意:这里,在停止 Nginx 服务前,已经将此 Nginx 从接入层网关中移除了,所以不会影响线上环境。为了避免使用新编译的 nginx 命令重启 Nginx 出现问题,这里通过脚本先停止 Nginx 服务,然后复制 nginx 命令后,再启动 Nginx 服务。


配置 Nginx 输出 debug 日志


在 Nginx 的 nginx.conf 文件中配置如下信息。


error_log  logs/error.log debug;
复制代码


此时,开启了 Nginx 的 debug 日志功能,并将 debug 信息输出到 error.log 文件中。


分析问题


接下来,在服务器命令行输入如下命令监听 error.log 文件的输出日志。


tail -F /usr/local/nginx/logs/error.log
复制代码


然后模拟访问 http 接口,可以看到 error.log 文件中输出如下信息。


2021/02/26 21:34:26 [debug] 31486#0: *56 http request line: "GET /third/system/base/thirdapp/get_detail HTTP/1.1"2021/02/26 21:34:26 [debug] 31486#0: *56 http uri: "/third/system/base/thirdapp/get_detail"2021/02/26 21:34:26 [debug] 31486#0: *56 http args: ""2021/02/26 21:34:26 [debug] 31486#0: *56 http exten: ""2021/02/26 21:34:26 [debug] 31486#0: *56 posix_memalign: 0000000000FF6450:4096 @162021/02/26 21:34:26 [debug] 31486#0: *56 http process request header line2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "Host: 10.31.5.66"2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "Accept: */*"2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "Accept-Encoding: gzip, deflate"2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "Referer: http://192.168.175.100/api/index.html"2021/02/26 21:34:26 [debug] 31486#0: *56 http header: "Connection: keep-alive"2021/02/26 21:34:26 [debug] 31486#0: *56 http header done2021/02/26 21:34:26 [debug] 31486#0: *56 rewrite phase: 02021/02/26 21:34:26 [debug] 31486#0: *56 test location: "/"2021/02/26 21:34:26 [debug] 31486#0: *56 test location: "file/"2021/02/26 21:34:26 [debug] 31486#0: *56 test location: ~ "/base"2021/02/26 21:34:26 [debug] 31486#0: *56 using configuration "/base"
复制代码


从上面的输出日志中,我们可以看到:访问的接口地址为“/third/system/base/thirdapp/get_detail”,如下所示。


2021/02/26 21:34:26 [debug] 31486#0: *56 http uri: "/third/system/base/thirdapp/get_detail"
复制代码


Nginx 在进行转发时,分别匹配了“/”,“file/”,“~/base”,最终将请求转发到了“/base”,如下所示。


2021/02/26 21:34:26 [debug] 31486#0: *56 test location: "/"2021/02/26 21:34:26 [debug] 31486#0: *56 test location: "file/"2021/02/26 21:34:26 [debug] 31486#0: *56 test location: ~ "/base"2021/02/26 21:34:26 [debug] 31486#0: *56 using configuration "/base"
复制代码


我们再来看看 Nginx 的配置,打开 nginx.conf 文件,找到下面的配置。


location ~/base {  proxy_pass                  http://base;  proxy_set_header Host $host:$server_port;}location ~/third {  proxy_pass                  http://third;  proxy_set_header Host $host:$server_port;}
复制代码


那么问题来了,访问的接口明明是“/third/system/base/thirdapp/get_detail”,为啥会走到“/base”下面呢?


说到这里,相信细心的小伙伴已经发现问题了,没错,又是运维的锅!!


解决问题


看了 Nginx 的配置后,相信很多小伙伴应该都知道如何解决问题了,没错那就是把 nginx.conf 中的如下配置。


location ~/base {  proxy_pass                  http://base;  proxy_set_header Host $host:$server_port;}location ~/third {  proxy_pass                  http://third;  proxy_set_header Host $host:$server_port;}
复制代码


修改为如下所示。


location /base {  proxy_pass                  http://base;  proxy_set_header Host $host:$server_port;}location /third {  proxy_pass                  http://third;  proxy_set_header Host $host:$server_port;}
复制代码


去掉“~”符号即可。


接下来,再次模拟访问 http 接口,能够正常访问接口。


接下来,将 Nginx 的 debug 功能关闭,也就是将 nginx.conf 文件中的 error_log logs/error.log debug; 配置注释掉,如下所示。


# error_log  logs/error.log debug;
复制代码


重新加载 nginx.conf 文件。


nginx_service.sh reload
复制代码


最终,将 Nginx 加入到接入层网关,问题解决。


科普 Nginx 的转发规则


Nginx 的 location 语法


location [=|~|~*|^~] /uri/ { … }
复制代码


  • = 严格匹配。如果请求匹配这个 location,那么将停止搜索并立即处理此请求

  • ~ 区分大小写匹配(可用正则表达式)

  • ~* 不区分大小写匹配(可用正则表达式)

  • !~ 区分大小写不匹配

  • !~* 不区分大小写不匹配

  • ^~ 如果把这个前缀用于一个常规字符串,那么告诉 nginx 如果路径匹配那么不测试正则表达式


示例 1:


location  / { }
复制代码


匹配任意请求


示例 2:


location ~* .(gif|jpg|jpeg)$ {    rewrite .(gif|jpg|jpeg)$ /logo.png;
复制代码


不区分大小写匹配任何以 gif、jpg、jpeg 结尾的请求,并将该请求重定向到 /logo.png 请求


示例 3:


location ~ ^.+\.txt$ {    root /usr/local/nginx/html/;}
复制代码


区分大小写匹配以.txt 结尾的请求,并设置此 location 的路径是/usr/local/nginx/html/。也就是以.txt 结尾的请求将访问/usr/local/nginx/html/ 路径下的 txt 文件


alias 与 root 的区别


  • root 实际访问文件路径会拼接 URL 中的路径

  • alias 实际访问文件路径不会拼接 URL 中的路径


示例如下:


location ^~ /binghe/ {     alias /usr/local/nginx/html/binghetic/;  }
复制代码


  • 请求:http://test.com/binghe/binghe1.html

  • 实际访问:/usr/local/nginx/html/binghetic/binghe1.html 文件


location ^~ /binghe/ {     root /usr/local/nginx/html/;  }
复制代码


  • 请求:http://test.com/binghe/binghe1.html

  • 实际访问:/usr/local/nginx/html/binghe/binghe1.html 文件


last 和 break 关键字的区别


(1)last 和 break 当出现在 location 之外时,两者的作用是一致的没有任何差异


(2)last 和 break 当出现在 location 内部时:


  • last 使用了 last 指令,rewrite 后会跳出 location 作用域,重新开始再走一次刚才的行为

  • break 使用了 break 指令,rewrite 后不会跳出 location 作用域,其整个生命周期都在当前 location 中。


permanent 和 redirect 关键字的区别


  • rewrite … permanent 永久性重定向,请求日志中的状态码为 301

  • rewrite … redirect 临时重定向,请求日志中的状态码为 302


综合实例


将符合某个正则表达式的 URL 重定向到一个固定页面


比如:我们需要将符合“/test/(\d+)/[\w-\.]+” 这个正则表达式的 URL 重定向到一个固定的页面。符合这个正则表达式的页面可能是:http://test.com/test/12345/abc122.htmlhttp://test.com/test/456/11111cccc.js


从上面的介绍可以看出,这里可以使用 rewrite 重定向或者 alias 关键字来达到我们的目的。因此,这里可以这样做:


(1)使用 rewrite 关键字


location ~ ^.+\.txt$ {    root /usr/local/nginx/html/;}location ~* ^/test/(\d+)/[\w-\.]+$ {    rewrite ^/test/(\d+)/[\w-\.]+$ /testpage.txt last;}
复制代码


这里将所有符合条件的 URL(PS:不区分大小写)都重定向到/testpage.txt 请求,也就是 /usr/local/nginx/html/testpage.txt 文件


(2)使用 alias 关键字


location ~* ^/test/(\d+)/[\w-\.]+$ {    alias /usr/local/nginx/html/binghetic/binghe1.html;}
复制代码


这里将所有符合条件的 URL(不区分大小写)都重定向到/usr/local/nginx/html/binghetic/binghe1.html 文件


好了,今天就到这儿吧,我是冰河,大家有啥问题可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你进群,一起交流技术,一起进阶,一起牛逼~~


发布于: 2021 年 03 月 29 日阅读数: 9
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

Mykit系列开源框架发起者、核心架构师和开发者,《海量数据处理与大数据技术实战》与《MySQL开发、优化与运维实战》作者。【冰河技术】微信公众号作者。

评论

发布
暂无评论
记一次生产环境大面积404问题!