剛工作那會,有一次,,上游調(diào)用我服務(wù)的老哥說,,你的服務(wù)報'502錯誤了,快去看看是為什么吧'。 當(dāng)時那個服務(wù)里正好有個調(diào)用日志,,平時會記錄各種200,4xx狀態(tài)碼的信息,。于是我跑到服務(wù)日志里去搜索了一下502這個數(shù)字,毫無發(fā)現(xiàn),。于是跟老哥說,,'服務(wù)日志里并沒有502的記錄,你是不是搞錯啦,?' 現(xiàn)在想來,,多少有些不好意思。 不知道有多少老哥是跟當(dāng)時的我是一樣的,,這篇文章,,就來聊聊 我們從狀態(tài)碼是什么開始聊起,。 HTTP狀態(tài)碼我們平時在瀏覽器里逛的某寶和某度,,其實都是一個個前端網(wǎng)頁。 一般來說,,前端并不存儲太多數(shù)據(jù),,大部分時候都需要從后端服務(wù)器那獲取數(shù)據(jù)。 于是前后端之間需要通過TCP協(xié)議去建立連接,,然后在TCP的基礎(chǔ)上傳輸數(shù)據(jù),。 而TCP是基于數(shù)據(jù)流的協(xié)議,傳輸數(shù)據(jù)時,,并不會為每個消息加入數(shù)據(jù)邊界,,直接使用裸的TCP進(jìn)行數(shù)據(jù)傳輸會有'粘包'問題。 因此需要用特地的協(xié)議格式去對數(shù)據(jù)進(jìn)行解析,。于是在此基礎(chǔ)上設(shè)計了HTTP協(xié)議,。詳細(xì)的內(nèi)容可以看我之前寫的《既然有HTTP協(xié)議,為什么還要有RPC》,。 比如,,我想要看某個商品的具體信息,其實就是前端發(fā)的HTTP請求中傳入商品的id,,后端返回的HTTP響應(yīng)中返回商品的價格,,商店名,發(fā)貨地址的信息等,。 這樣,,表面上,我們是在刷著各種網(wǎng)頁,,實際上背后正有多次HTTP消息在不斷進(jìn)行收發(fā),。 但問題就來了,,上面提到的都是正常情況,如果有異常情況呢,,比如前端發(fā)的數(shù)據(jù),,根本就不是個商品id,而是一張圖片,,這對于后端服務(wù)端來說是不可能給出正常響應(yīng)的,,于是就需要設(shè)計一套HTTP狀態(tài)碼,用來標(biāo)識這次HTTP請求響應(yīng)流程是否正常,。通過這個可以影響瀏覽器的行為,。 比方說一切正常,那服務(wù)端返回個 但問題就來了,。 服務(wù)端都有問題了,搞嚴(yán)重點,,服務(wù)器可能直接就崩潰了,那它還怎么給你返回狀態(tài)碼,? 是的,,這種情況,服務(wù)端是不可能給客戶端返回狀態(tài)碼的,。所以說,,一般情況下5xx的狀態(tài)碼其實并不是服務(wù)器返回給客戶端的。 它們是由網(wǎng)關(guān)返回的,,常見的網(wǎng)關(guān),,比如 nginx的作用回到前后端交互數(shù)據(jù)的話題上,,如果前端用戶少,,那后端處理起請求來,,游刃有余。但隨著用戶越來越多,,后端服務(wù)器受資源限制,,cpu或者內(nèi)存都可能會嚴(yán)重不足,這時候解決方案也很簡單,,多搞幾臺一樣的服務(wù)器,,這樣就能將這些前端請求均攤給幾個服務(wù)器,從而提升處理能力,。 但要實現(xiàn)這樣的效果,,前端就得知道后端具體有哪些個服務(wù)器,并一一跟他們建立TCP連接,。 也不是不行,,但就是麻煩。 但這時候如果能有個中間層擋在它們中間就好了,,這樣客戶端只需要跟中間層連接,,中間層再和服務(wù)器建立連接。 于是,,這個中間層就成了這幫服務(wù)器的一個代理人一樣,,客戶端有啥事都找代理人,只管發(fā)出自己的請求,,再由代理人去找某個服務(wù)器去完成響應(yīng),。整個過程下來,客戶端只知道自己的請求被代理人幫忙搞定了,,但代理人具體找了那個服務(wù)器去完成,,客戶端并不知道,也不需要知道,。 像這種,,屏蔽掉具體有哪些服務(wù)器的代理方式就是所謂的反向代理。
而這個中間層的角色,,一般由 另外,由于背后的服務(wù)器可能性能配置各不相同,,有些4核8G,,有些2核4G,nginx能為它們加上不同的訪問權(quán)重,,權(quán)重高的多轉(zhuǎn)發(fā)點請求,,通過這個方式實現(xiàn)不同的負(fù)載均衡策略,。 nginx返回5xx狀態(tài)碼有了nginx這一中間層后,客戶端從直連服務(wù)端,,變成客戶端直連nginx,,再由nginx直連服務(wù)端。從一個TCP連接變成兩個TCP連接,。 于是,,當(dāng)服務(wù)器發(fā)生異常時,nginx發(fā)送給服務(wù)器的那條TCP連接就不能正常響應(yīng),,nginx在得到這一信息后,,就會返回5xx錯誤碼給客戶端,也就是說5xx的報錯,,其實是由nginx識別出來,,并返回給客戶端的,服務(wù)端本身,,并不會有5xx的日志信息,。所以才會出現(xiàn)文章開頭的一幕,上游收到了我服務(wù)的502報錯,,但我在自己的服務(wù)日志里卻搜索不到這一信息,。 產(chǎn)生502的常見原因在 502 Bad Gateway
汝聽,人言否,? 這對于大部分編程小白來說,,不僅沒解釋到問題,反而只會冒出更多的問號,。比如,,這上面提到的無效響應(yīng)到底指的是什么? 我來解釋下,,它其實是說,,502其實是由網(wǎng)關(guān)代理(nginx)發(fā)出的,是因為網(wǎng)關(guān)代理把客戶端的請求轉(zhuǎn)發(fā)給了服務(wù)端,,但服務(wù)端卻發(fā)出了無效響應(yīng),,而這里的無效響應(yīng),,一般是指TCP的 四次揮手估計大家背的很熟了,所以略過,,我們來重點說下 RST是什么,?我們都知道TCP正常情況下斷開連接是用四次揮手,那是正常時候的優(yōu)雅做法,。 但異常情況下,,收發(fā)雙方都不一定正常,連揮手這件事本身都可能做不到,,所以就需要一個機(jī)制去強(qiáng)行關(guān)閉連接,。 RST 就是用于這種情況,一般用來異常地關(guān)閉一個連接,。它是TCP包頭中的一個標(biāo)志位,,在收到置這個標(biāo)志位的數(shù)據(jù)包后,連接就會被關(guān)閉,,此時接收到 RST的一方,,在應(yīng)用層會看到一個 而之所以發(fā)出RST報文,,一般有兩個常見原因,。 服務(wù)端過早斷開連接nginx與服務(wù)端之間有一條TCP連接,在nginx將客戶端請求轉(zhuǎn)發(fā)給服務(wù)端時,,他兩之間按道理會一直保持這條連接,,直到服務(wù)端將結(jié)果正常返回后,再斷開連接,。 但如果服務(wù)端過早斷開連接,,而 過早斷開連接的原因常見的有兩個。 第一個是,,服務(wù)端設(shè)置的超時時間過短,。不管是用的哪種編程語言,一般都有現(xiàn)成的 比如你的接口處理時間是 遇到這種問題,將 第二個原因,,也是造成502狀態(tài)碼最常見的原因,就是服務(wù)端應(yīng)用進(jìn)程崩了(crash),。 服務(wù)端崩了,,也就是當(dāng)前沒有一個進(jìn)程在監(jiān)聽服務(wù)器端口,而此時你卻嘗試向一個不存在的端口發(fā)數(shù)據(jù),,服務(wù)器的linux內(nèi)核協(xié)議棧就會響應(yīng)一個RST數(shù)據(jù)包,。同樣,這時候nginx也會給客戶端一個502,。 在開發(fā)過程中,,這種情況是最常見的。 現(xiàn)在我們大部分的服務(wù)器都會將掛掉的服務(wù)重啟,因此我們需要判斷下服務(wù)是否曾經(jīng)崩潰過。 如果你有對服務(wù)端的cpu或者內(nèi)存做過監(jiān)控,,可以看下CPU或內(nèi)存的監(jiān)控圖是否出現(xiàn)過斷崖式的突然下跌,。如果有,,十有八九百,就是你的服務(wù)端應(yīng)用程序曾經(jīng)崩潰過。 除此之外你還通過下面的命令,看下進(jìn)程上次的啟動時間是什么時候,。
比如我要看的進(jìn)程id是13515,命令就需要像下面這樣,。 # ps -o lstart 13515 可以看到它上次的啟動時間是8月31日,,這個時間如果跟你印象中的操作時間有差距,那說明進(jìn)程可能是崩了之后被重新拉起了,。 遇到這種問題,,最重要的是找出崩潰的原因,崩潰的原因就多種多樣了,,比如,,對未初始化的內(nèi)存地址進(jìn)行寫操作,或者內(nèi)存訪問越界(數(shù)組arr長度明明只有2,,代碼卻讀arr[3]),。 這種情況幾乎都是程序有代碼邏輯問題,崩潰一般也會留下代碼堆棧,可以根據(jù)堆棧報錯去排查問題,,修復(fù)之后就好了。比如下面這張圖是golang的報錯堆棧信息,,其他語言的也類似,。 不打印堆棧的情況但有一些情況,有時候根本不留下堆棧,。 比如內(nèi)存泄露導(dǎo)致進(jìn)程占用內(nèi)存越來越多,,最后導(dǎo)致超過服務(wù)器的最大內(nèi)存限制,觸發(fā) 還有更隱蔽的,,代碼邏輯里隱藏了主動退出進(jìn)程的操作。比如golang的日志打印里有個方法叫 如果你很明確,,你的服務(wù)沒有崩過,。那繼續(xù)往下看。 網(wǎng)關(guān)將請求打到了一個不存在的IP上nginx是通過配置的形式來代理多個服務(wù)器,。這個配置一般是放在 打開它,你可能會看到類似下面這樣的信息,。
上面配置的含義是,,如果客戶端訪問 可以看出,,nginx具有相當(dāng)豐富的配置能力。但要注意的是,,這些個文件是需要自己手動配置的,。對于服務(wù)器少,且不怎么變化的情況,,這當(dāng)然沒問題,。 但現(xiàn)在已經(jīng)是云原生時代了,很多公司內(nèi)部都有自己的云產(chǎn)品,,服務(wù)自然也會上云,。一般來說每次更新服務(wù),都可能會將服務(wù)部署到一臺新的機(jī)器上。而這個ip也會隨著改變,,難道每發(fā)布一次服務(wù),,都需要手動去nginx上改配置嗎?這顯然不現(xiàn)實,。 如果能在服務(wù)啟動時,,讓服務(wù)主動將自己的ip告訴nginx,然后nginx自己生成這樣的一個配置并重新加載,,那事情就簡單多了,。 為了實現(xiàn)這樣一個服務(wù)注冊的功能,不少公司都會基于nginx進(jìn)行二次開發(fā),。 但如果這個服務(wù)注冊功能有問題,,比方說服務(wù)啟動后,新服務(wù)沒注冊上,,但老服務(wù)已經(jīng)被銷毀了,。這時候nginx還將請求打到老服務(wù)的IP上,由于老服務(wù)所在的機(jī)器已經(jīng)沒有這個服務(wù)了,,所以服務(wù)器內(nèi)核就會響應(yīng)RST,,nginx收到RST后回復(fù)502給客戶端。 要排查這種問題也不難,。 這個時候,,你可以看下nginx側(cè)是否有打印相關(guān)的日志,看下轉(zhuǎn)發(fā)的IP端口是否符合預(yù)期,。 如果不符合預(yù)期,,可以去找找做這個基礎(chǔ)組件的同事,進(jìn)行一波友好的交流,。 總結(jié)
最后最近原創(chuàng)更文的閱讀量穩(wěn)步下跌,思前想后,,夜里輾轉(zhuǎn)反側(cè),。 我有個不成熟的請求。 離開廣東好長時間了,,好久沒人叫我靚仔了,。 大家可以在評論區(qū)里,叫我一靚仔嗎,? 我這么善良質(zhì)樸的愿望,,能被滿足嗎? 如果實在叫不出口的話,,能幫我點下關(guān)注和右下角的點贊+收藏嗎,? 別說了,一起在知識的海洋里嗆水吧 |
|