1. 程式人生 > >記一次生產環境大面積404問題!

記一次生產環境大面積404問題!

## 寫在前面 釋出到線上的介面服務一直好端端的,今天突然運營反饋說很多功能無法正常使用。經過排查,發現前端呼叫後端介面時,部分接口出現404的現象。今天,我到公司比較晚,肯定是哪個小夥伴昨晚下班,走出辦公室前沒有祈禱伺服器不要出問題。要把這個人揪出來,吊在伺服器上——祭天! 文章已收錄到: [https://github.com/sunshinelyz/technology-binghe](https://github.com/sunshinelyz/technology-binghe) [https://gitee.com/binghe001/technology-binghe](https://gitee.com/binghe001/technology-binghe) ## 問題復現 得知運營的反饋後,我迅速登入伺服器排查問題。首先,查看了介面服務的啟動程序正常。驗證介面服務的ip和埠是否正常,結果也是沒啥問題。接下來,通過Nginx轉發請求,此時出現了問題,無法訪問介面。同時Nginx的access.log檔案中輸出瞭如下日誌資訊。 ```bash 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時的配置情況。 ```bash nginx -V ``` 注意:這裡已經為Nginx配置了系統環境變數,如果沒有配置系統環境變數,則需要輸入nginx命令所在目錄的完整路徑,例如: ```bash /usr/local/nginx/sbin/nginx -v ``` 命令列輸出瞭如下資訊。 ```bash 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。 ```bash 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服務。 ```bash 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檔案中配置如下資訊。 ```bash error_log logs/error.log debug; ``` 此時,開啟了Nginx的debug日誌功能,並將debug資訊輸出到error.log檔案中。 ### 分析問題 接下來,在伺服器命令列輸入如下命令監聽error.log檔案的輸出日誌。 ```bash tail -F /usr/local/nginx/logs/error.log ``` 然後模擬訪問http介面,可以看到error.log檔案中輸出如下資訊。 ```bash 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" ``` 從上面的輸出日誌中,我們可以看到:訪問的介面地址為“/third/system/base/thirdapp/get_detail”,如下所示。 ```bash 2021/02/26 21:34:26 [debug] 31486#0: *56 http uri: "/third/system/base/thirdapp/get_detail" ``` Nginx在進行轉發時,分別匹配了“/”,“file/”,“~/base”,最終將請求轉發到了“/base”,如下所示。 ```bash 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檔案,找到下面的配置。 ```bash 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中的如下配置。 ```bash 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; } ``` 修改為如下所示。 ```bash 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;` 配置註釋掉,如下所示。 ```bash # error_log logs/error.log debug; ``` 重新載入nginx.conf檔案。 ```bash nginx_service.sh reload ``` 最終,將Nginx加入到接入層閘道器,問題解決。 ## 科普Nginx的轉發規則 ### Nginx的location語法 ```bash location [=|~|~*|^~] /uri/ { … } ``` - = 嚴格匹配。如果請求匹配這個location,那麼將停止搜尋並立即處理此請求 - ~ 區分大小寫匹配(可用正則表示式) - ~* 不區分大小寫匹配(可用正則表示式) - !~ 區分大小寫不匹配 - !~* 不區分大小寫不匹配 - ^~ 如果把這個字首用於一個常規字串,那麼告訴nginx 如果路徑匹配那麼不測試正則表示式 示例1: ```bash location / { } ``` 匹配任意請求 示例2: ```bash location ~* .(gif|jpg|jpeg)$ { rewrite .(gif|jpg|jpeg)$ /logo.png; } ``` 不區分大小寫匹配任何以gif、jpg、jpeg結尾的請求,並將該請求重定向到 /logo.png請求 示例3: ```bash 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中的路徑 示例如下: ```bash location ^~ /binghe/ { alias /usr/local/nginx/html/binghetic/; } ``` - 請求:http://test.com/binghe/binghe1.html - 實際訪問:/usr/local/nginx/html/binghetic/binghe1.html 檔案 ```bash 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.html、http://test.com/test/456/11111cccc.js等 從上面的介紹可以看出,這裡可以使用rewrite重定向或者alias關鍵字來達到我們的目的。因此,這裡可以這樣做: (1)使用rewrite關鍵字 ```bash 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關鍵字 ```bash location ~* ^/test/(\d+)/[\w-\.]+$ { alias /usr/local/nginx/html/binghetic/binghe1.html; } ``` 這裡將所有符合條件的URL(不區分大小寫)都重定向到/usr/local/nginx/html/binghetic/binghe1.html 檔案 **好了,今天就到這兒吧,我是冰河,大家有啥問題可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你進群,一起交流技術,一起進階,一起