Go BIO/NIO探討:Gin框架中如何處理HTTP請求

最近看到字節(jié)跳動開源了Go語言的Hertz,聲稱使用了 Non-blocking IO 網(wǎng)絡庫 Netpoll,所以性能非常強大,并給出了Echo的性能測試數(shù)據(jù)。

性能測試
相對于目前業(yè)界流行的 gin 框架,QPS提升超過100%,而且單次請求的數(shù)據(jù)包越大,性能提升越明顯。
熟悉Java的朋友看到 NIO 這個詞,應該會有莫名的親切感。值得慶幸的是,現(xiàn)在終于有人用Go翻譯一波netty了。
在這之前,為了更好地理解Blocking IO,我們從 Web 框架入手,看 Go 內(nèi)置的 net/http 是如何工作的。
下面這段代碼展示了Gin框架中如何定義一個路由,并啟動Server。代碼來自于 Github:gin-gonic/gin,我們從這段代碼入手,了解 http 的運行機制。
在這段代碼中,r 是一個 *gin.Engine 對象,它實現(xiàn)了 net/http下的Handler 接口:
第二步是設置路由和對應的處理函數(shù),*gin.Engine 對象嵌入了 struct RouterGroup,實現(xiàn)路由功能。Web開發(fā)中,這是所有業(yè)務邏輯的入口。
第三步是運行服務,基本流程是三個tcp系統(tǒng)調(diào)用 socket, bind, listen,然后在 for 循環(huán)里調(diào)用 accept 接收新的tcp連接(更多細節(jié)參考Unix網(wǎng)絡編程卷一第四章)。
到這里,HTTP還沒有登場,因為HTTP是應用層協(xié)議,而TCP是傳輸層協(xié)議。我們拿到 tcp connection 以后,HTTP協(xié)議體現(xiàn)在對數(shù)據(jù)包的處理上。

HTTP/1 仍然是一個超文本協(xié)議,HTTP/2 是一個基于幀的二進制協(xié)議,并且支持了server端主動推送。由于 HTTP/1 仍然是主流,我們這里先說 HTTP/1,它的報文分三部分:
- 起始行
 - Headers 消息頭,以一個空行結(jié)束
 - Body 消息體
 

應用程序可以方便地解析HTTP 請求和響應。HTTP/1.1 已經(jīng)支持復用TCP連接,也就是說多個HTTP請求可以在同一個tcp conn上傳輸,那么client端如何把request和response 對應起來呢?會有什么問題?HTTP/2中又是怎么做的?這里先挖個坑。
繼續(xù)看 net/http 的代碼,了解HTTP 如何在tcp conn之上工作的。主要邏輯如下:
這段代碼的前半部分是設置cleanup邏輯、初始化conn相關(guān)的變量,后半部分是在 for 循環(huán)里處理 HTTP 請求,體現(xiàn)為:
- 讀取 tcp conn上的數(shù)據(jù),并解析&組裝成一個請求,存儲到一個http.response對象里。
 - 通過 ServeHTTP 調(diào)用業(yè)務邏輯(路由轉(zhuǎn)發(fā)到特定處理函數(shù)),獲取響應。
 - 把響應報文寫入 tcp conn。
 
關(guān)于Gin框架和net/http 的邏輯到這里就介紹完了,大的流程參考下面這個思維導圖:

為什么 Blocking IO
當我們聊到 Blocking IO 和 Non-blocking IO,通常是指一個線程調(diào)用 read 或 write 時,是否被阻塞:
- BIO: 線程被阻塞直到讀到數(shù)據(jù)或?qū)懭胪瓿伞?/span>
 - NIO: 線程不被阻塞,可以去做其他事情,但是有數(shù)據(jù)到來或者寫入完成時,線程會接收到通知。
 
這里我們限定到網(wǎng)絡IO的情況,Go net/http 里BIO體現(xiàn)在兩個地方:
- case 1: listener 在for循環(huán)里等待接收新的tcp conn。
 - case 2: conn 等待讀取新的 request。
 
如果把 goroutine 當成操作系統(tǒng)線程,我們可以把這種模式當作BIO。由于在網(wǎng)絡IO上 Go語言對 goroutine 的特殊實現(xiàn),在細節(jié)上可能會有點爭議,有興趣的同學可以自己研究下。
要做到 NIO,典型的方式是通過 reactor 模式,替換上面的兩個for循環(huán)。在后面的文章中,我們介紹 hertz 框架的時候,會詳細聊一下這個方案。















 
 
 













 
 
 
 