記一次生產(chǎn)環(huán)境大面積404問(wèn)題!
作者個(gè)人研發(fā)的在高并發(fā)場(chǎng)景下,提供的簡(jiǎn)單、穩(wěn)定、可擴(kuò)展的延遲消息隊(duì)列框架,具有精準(zhǔn)的定時(shí)任務(wù)和延遲隊(duì)列處理功能。自開(kāi)源半年多以來(lái),已成功為十幾家中小型企業(yè)提供了精準(zhǔn)定時(shí)調(diào)度方案,經(jīng)受住了生產(chǎn)環(huán)境的考驗(yàn)。為使更多童鞋受益,現(xiàn)給出開(kāi)源框架地址:https://github.com/sunshinelyz/mykit-delay
寫在前面
發(fā)布到線上的接口服務(wù)一直好端端的,今天突然運(yùn)營(yíng)反饋說(shuō)很多功能無(wú)法正常使用。經(jīng)過(guò)排查,發(fā)現(xiàn)前端調(diào)用后端接口時(shí),部分接口出現(xiàn)404的現(xiàn)象。今天,我到公司比較晚,肯定是哪個(gè)小伙伴昨晚下班,走出辦公室前沒(méi)有祈禱服務(wù)器不要出問(wèn)題。要把這個(gè)人揪出來(lái),吊在服務(wù)器上——祭天!
文章已收錄到:
https://github.com/sunshinelyz/technology-binghe
https://gitee.com/binghe001/technology-binghe
問(wèn)題復(fù)現(xiàn)
得知運(yùn)營(yíng)的反饋后,我迅速登錄服務(wù)器排查問(wèn)題。首先,查看了接口服務(wù)的啟動(dòng)進(jìn)程正常。驗(yàn)證接口服務(wù)的ip和端口是否正常,結(jié)果也是沒(méi)啥問(wèn)題。接下來(lái),通過(guò)Nginx轉(zhuǎn)發(fā)請(qǐng)求,此時(shí)出現(xiàn)了問(wèn)題,無(wú)法訪問(wèn)接口。同時(shí)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"
此時(shí),從Nginx日志中發(fā)現(xiàn),輸出的狀態(tài)為404,未找到后端的接口服務(wù)。為了進(jìn)一步定位問(wèn)題,我直接在線上環(huán)境通過(guò)curl命令的方式來(lái)訪問(wèn)接口服務(wù),結(jié)果是正常的。
經(jīng)過(guò)這一系列的操作之后,我們就可以確定問(wèn)題是出在Nginx上了。
問(wèn)題分析
Nginx開(kāi)啟debug模塊
既然已經(jīng)定位到問(wèn)題了,那我們接下來(lái)就要分析下產(chǎn)生問(wèn)題的具體原因了。既然是Nginx的問(wèn)題,我第一時(shí)間想到的就是調(diào)試Nginx查找錯(cuò)誤原因。于是我在服務(wù)器命令行輸入了如下命令來(lái)查看安裝Nginx時(shí)的配置情況。
- nginx -V
注意:這里已經(jīng)為Nginx配置了系統(tǒng)環(huán)境變量,如果沒(méi)有配置系統(tǒng)環(huán)境變量,則需要輸入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時(shí)沒(méi)有配置Nginx的debug模塊。
于是我在服務(wù)器上找到了Nginx的安裝文件,在命令行輸入如下命令重新編譯Nginx。
- cd /usr/local/src/nginx/ #進(jìn)入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 #設(shè)置編譯Nginx的配置信息
- make #編譯Nginx,切記不要輸入make install
上述命令中,切記不要輸入make install 進(jìn)行安裝。
執(zhí)行完 make 命令后,會(huì)在當(dāng)前目錄的objs目錄下生成nginx命令,此時(shí)我們需要先停止Nginx服務(wù),備份/usr/local/nginx/sbin/目錄下的nginx命令,然后將objs目錄下的nginx命令復(fù)制到/usr/local/nginx/sbin/目錄下,然后啟動(dòng)Nginx服務(wù)。
- nginx_service.sh stop #通過(guò)腳本停止Nginx服務(wù)
- mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak #備份原有nginx命令
- cp ./objs/nginx /usr/local/nginx/sbin/nginx #復(fù)制nginx命令
- nginx_service.sh start #通過(guò)腳本啟動(dòng)Nginx服務(wù)
注意:這里,在停止Nginx服務(wù)前,已經(jīng)將此Nginx從接入層網(wǎng)關(guān)中移除了,所以不會(huì)影響線上環(huán)境。為了避免使用新編譯的nginx命令重啟Nginx出現(xiàn)問(wèn)題,這里通過(guò)腳本先停止Nginx服務(wù),然后復(fù)制nginx命令后,再啟動(dòng)Nginx服務(wù)。
配置Nginx輸出debug日志
在Nginx的nginx.conf文件中配置如下信息。
- error_log logs/error.log debug;
此時(shí),開(kāi)啟了Nginx的debug日志功能,并將debug信息輸出到error.log文件中。
分析問(wèn)題
接下來(lái),在服務(wù)器命令行輸入如下命令監(jiān)聽(tīng)error.log文件的輸出日志。
- tail -F /usr/local/nginx/logs/error.log
然后模擬訪問(wèn)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 @16
- 2021/02/26 21:34:26 [debug] 31486#0: *56 http process request header line
- 2021/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 done
- 2021/02/26 21:34:26 [debug] 31486#0: *56 rewrite phase: 0
- 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"
從上面的輸出日志中,我們可以看到:訪問(wèn)的接口地址為“/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在進(jìn)行轉(zhuǎn)發(fā)時(shí),分別匹配了“/”,“file/”,“~/base”,最終將請(qǐng)求轉(zhuǎn)發(fā)到了“/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"
我們?cè)賮?lái)看看Nginx的配置,打開(kāi)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;
- }
那么問(wèn)題來(lái)了,訪問(wèn)的接口明明是“/third/system/base/thirdapp/get_detail”,為啥會(huì)走到“/base”下面呢?
說(shuō)到這里,相信細(xì)心的小伙伴已經(jīng)發(fā)現(xiàn)問(wèn)題了,沒(méi)錯(cuò),又是運(yùn)維的鍋!!
解決問(wèn)題
看了Nginx的配置后,相信很多小伙伴應(yīng)該都知道如何解決問(wèn)題了,沒(méi)錯(cuò)那就是把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;
- }
去掉“~”符號(hào)即可。
接下來(lái),再次模擬訪問(wèn)http接口,能夠正常訪問(wèn)接口。
接下來(lái),將Nginx的debug功能關(guān)閉,也就是將nginx.conf文件中的 error_log logs/error.log debug; 配置注釋掉,如下所示。
- # error_log logs/error.log debug;
重新加載nginx.conf文件。
- nginx_service.sh reload
最終,將Nginx加入到接入層網(wǎng)關(guān),問(wèn)題解決。
科普Nginx的轉(zhuǎn)發(fā)規(guī)則
Nginx的location語(yǔ)法
- location [=|~|~*|^~] /uri/ { … }
- = 嚴(yán)格匹配。如果請(qǐng)求匹配這個(gè)location,那么將停止搜索并立即處理此請(qǐng)求
- ~ 區(qū)分大小寫匹配(可用正則表達(dá)式)
- ~* 不區(qū)分大小寫匹配(可用正則表達(dá)式)
- !~ 區(qū)分大小寫不匹配
- !~* 不區(qū)分大小寫不匹配
- ^~ 如果把這個(gè)前綴用于一個(gè)常規(guī)字符串,那么告訴nginx 如果路徑匹配那么不測(cè)試正則表達(dá)式
示例1:
- location / { }
匹配任意請(qǐng)求
示例2:
- location ~* .(gif|jpg|jpeg)$ {
- rewrite .(gif|jpg|jpeg)$ /logo.png;
- }
不區(qū)分大小寫匹配任何以gif、jpg、jpeg結(jié)尾的請(qǐng)求,并將該請(qǐng)求重定向到 /logo.png請(qǐng)求
示例3:
- location ~ ^.+\.txt$ {
- root /usr/local/nginx/html/;
- }
區(qū)分大小寫匹配以.txt結(jié)尾的請(qǐng)求,并設(shè)置此location的路徑是/usr/local/nginx/html/。也就是以.txt結(jié)尾的請(qǐng)求將訪問(wèn)/usr/local/nginx/html/ 路徑下的txt文件
alias與root的區(qū)別
- root 實(shí)際訪問(wèn)文件路徑會(huì)拼接URL中的路徑
- alias 實(shí)際訪問(wèn)文件路徑不會(huì)拼接URL中的路徑
示例如下:
- location ^~ /binghe/ {
- alias /usr/local/nginx/html/binghetic/;
- }
- 請(qǐng)求:http://test.com/binghe/binghe1.html
- 實(shí)際訪問(wèn):/usr/local/nginx/html/binghetic/binghe1.html 文件
- location ^~ /binghe/ {
- root /usr/local/nginx/html/;
- }
- 請(qǐng)求:http://test.com/binghe/binghe1.html
- 實(shí)際訪問(wèn):/usr/local/nginx/html/binghe/binghe1.html 文件
last 和 break關(guān)鍵字的區(qū)別
(1)last 和 break 當(dāng)出現(xiàn)在location 之外時(shí),兩者的作用是一致的沒(méi)有任何差異
(2)last 和 break 當(dāng)出現(xiàn)在location 內(nèi)部時(shí):
- last 使用了last 指令,rewrite 后會(huì)跳出location 作用域,重新開(kāi)始再走一次剛才的行為
- break 使用了break 指令,rewrite后不會(huì)跳出location 作用域,其整個(gè)生命周期都在當(dāng)前l(fā)ocation中。
permanent 和 redirect關(guān)鍵字的區(qū)別
- rewrite … permanent 永久性重定向,請(qǐng)求日志中的狀態(tài)碼為301
- rewrite … redirect 臨時(shí)重定向,請(qǐng)求日志中的狀態(tài)碼為302
綜合實(shí)例
將符合某個(gè)正則表達(dá)式的URL重定向到一個(gè)固定頁(yè)面
比如:我們需要將符合“/test/(\d+)/[\w-.]+” 這個(gè)正則表達(dá)式的URL重定向到一個(gè)固定的頁(yè)面。符合這個(gè)正則表達(dá)式的頁(yè)面可能是:http://test.com/test/12345/abc122.html、http://test.com/test/456/11111cccc.js等
從上面的介紹可以看出,這里可以使用rewrite重定向或者alias關(guān)鍵字來(lái)達(dá)到我們的目的。因此,這里可以這樣做:
(1)使用rewrite關(guān)鍵字
- location ~ ^.+\.txt$ {
- root /usr/local/nginx/html/;
- }
- location ~* ^/test/(\d+)/[\w-\.]+$ {
- rewrite ^/test/(\d+)/[\w-\.]+$ /testpage.txt last;
- }
這里將所有符合條件的URL(PS:不區(qū)分大小寫)都重定向到/testpage.txt請(qǐng)求,也就是 /usr/local/nginx/html/testpage.txt 文件
(2)使用alias關(guān)鍵字
- location ~* ^/test/(\d+)/[\w-\.]+$ {
- alias /usr/local/nginx/html/binghetic/binghe1.html;
- }
這里將所有符合條件的URL(不區(qū)分大小寫)都重定向到/usr/local/nginx/html/binghetic/binghe1.html 文件
本文轉(zhuǎn)載自微信公眾號(hào)「冰河技術(shù)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系冰河技術(shù)公眾號(hào)。