使用 fasthttp 時要注意的兩個點

我們做的是聚合支付系統(tǒng),使用的是fasthttp 作為http server, http client 也是使用fasthttp

1. 第一個問題出現的場景是我們使用fasthttp client 請求微信支付時報了這個err ErrConnectionClosed

the server closed connection before returning the first response byte Make sure the server returns 'Connection: close' response header before closing the connection
image.png

由于是線上問題 情況緊急,查了一些資料后覺得使用fasthttp client 估計有坑,零時換成 go 原生 http client ,果然解決了問題。

那么問題來了,怎么會出現這中情況呢,這個錯誤的字面意思是 鏈接被對端關閉了,說到這里 就有一個要 注意了,fasthttp 默認使用的是 http1.1 golang 里面 默認是 keep-live 保持長鏈接的,但是對方關閉了鏈接我們 發(fā)包時 報的錯誤 應該是reset 呀,怎么是這么個玩意兒,只能抓包看看 啰, 結果發(fā)現 我們一個請求過去正?;匕螅?秒過后 又收到一個FIN 包,此時就懷疑微信的 keep-live 時間了 ,那么 我就打印了一下 回包的head ,果然 keep-live 時間就是8s ,也就是說 8s 后微信 關閉了鏈接,不巧 我們的fasthttp 空閑等待時間是 15s 。還是沒弄明白,按道理對端關閉了 ,client 端 應該是有感知的, 而且換成了 golang 原生 http client 就解決了問題,說明原生的client 是 有感知的,只能看代碼了,結果發(fā)現了有意思的


image.png

從截圖可以看出來,這個fasthttp client write 和 read是 同步的
如果第一個請求 之后 8s 之內不再請求 ,那么我們是不會再進行read 操作的,那么就無法感知 已經 被server 端關閉了。此時問題又來了, 8s 后請求 對端已經發(fā)了 FIN 我們再次write 應該是 Connection reset by peer 錯誤吧,其實不對 ,這里就涉及 tcp 的 四次揮手了, server 發(fā)了FIN 包 只能說明 server 不再 write 了 ,但是 server 是可以再read 的 ,也就是說 client 是可以再 wirte 的,剛好錯誤就是在第二次 請求 的read 操作時報出的。這里證明了錯誤的產生原因。

但是有 一個疑問 沒解決,為什么用 原生 http client 沒問題, 進一步來分析下,看代碼


image.png

可以看到兩個 協(xié)程 在 loop write read,所以對 server 端 FIN 是 及時響應的,也就是client 及時也關閉了鏈接。
問題找到了如何解決呢,兩個方法:

  1. 簡單粗暴,直接 設置 head 請求頭 keep-live close ,變成短鏈接,但是這個有損性能。
  2. 既然 微信 keep-live 是 8s 那么 只要 設置 IdleConnTimeout 小于 8s 就行了 比如 7s , 這樣就可以先于 微信 關閉連接了,不會讓新的請求 使用 對端 發(fā)了 FIN 的鏈接 。

第二個問題 fasthttp 優(yōu)雅關閉關不掉

我們使用了 fasthttp 作為 http server . 為了保證 業(yè)務完整性,做了一個 優(yōu)雅關閉功能 ,其實優(yōu)雅關閉思路很簡單:
收到 signal QUIT 后 首先close 調 監(jiān)聽 端口, 然后等所有的鏈接上的業(yè)務處理完畢后 退出程序 就OK 了 , 那么如果 保證 所有鏈接處理完畢呢, 計數器!!! 新建一個鏈接時 +1 關閉一個鏈接時 -1 , 具體實現 其實就是實現 net.Listener 的 accept 和 close 接口 ,例如:


image.png

實現思路 是這樣的 按道理 就OK 了,其實不是的,我是這么測試的:用一個 golang client for 循環(huán) 每隔5秒 發(fā)一個 請求, 然后在server 端 發(fā)送kill signal QUIT 結果發(fā)現程序一直沒有退出, 怎么回事呢 ,除非 鏈接數 一直沒有 減到 0 ,后來打印日志發(fā)現 確實沒有,但是沒道理啊, 一個請求處理了應該 close 呀, 此時 又想起了 http1.1 默認 keep-live 我每隔5秒 發(fā)一個請求,而設置的 server 最大空閑等待時間為15分鐘, 也就是雖然 listener 關閉了 不會再有新的鏈接進來,但是 正在跑的這個鏈接永遠不會主動關閉,因為每隔五秒 就有一個請求
怎么解決呢 ? 想到一個方法,就是設置一個標志判斷是否程序 被關閉,當收到了signal QUIT 之后 就把正在處理的 鏈接的 resonse head 設置 connection close ,如何設置呢, 因為 我們我們處理的請求 都是在 一個 fasthttp.RequestHandler
,可以包裝一下 這個 函數 例如:


image.png

這樣我們每個請求完畢之后,檢測到stop 標志后 必定處理會關閉鏈接。
貌似 解決了,其實還有點漏洞,就是這個stop 標志的檢測 是代碼要運行到這里,如果設置了stop 標志代碼,但此時這條鏈接上的沒有請求過來怎么辦(j假如我是10分鐘發(fā)一個請求),那么只能等待 IdleConnTimeout 超時的時候關閉鏈接了,但是 我們關閉一個程序的時候 不希望等待那么久,可以看看 這段代碼:


image.png

listener 有一個maxWaitTime,就是程序無論有沒有包在處理,到了這個時間程序 一定要退出,比如這個時間設置為 3秒,當然這個問題 也是有漏洞的萬一 我們一個請求 處理耗時 不止3s 呢,豈不是不能完整處理這個請求, 當然我們業(yè)務一般一個請求一秒就處理完了。 不過這個問題確實存在,目前我還沒想到一個好的辦法徹底解決這個問題,如果你們有想法可以告訴我一下。

總結一下,這些主要是關于fasthttp 使用時要注意的問題,http協(xié)議 get post 雖然簡單,但是要用好 還是要好好研究下, tcp 協(xié)議也要好好理解下,三次握手,四次揮手 還是有很多值得深究的,也很有意思。今天太冷就不多寫了,寫的有問題之處,還望多多指點。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容