《Go組件設計與實現》-netpoll的總結


主要針對字節跳動的netpoll網絡庫進行總結。netpoll網絡庫相比於go本身的net標准庫更適合高並發場景。

基礎知識

netpoll與go.net庫一樣使用epoll這種IO多路復用機制處理網絡請求。

基本理解

我們知道linux萬物皆文件,每個文件有個文件標識符fd,我們可以想象linux提供給我們的socket fd就是操作系統將傳輸層及以下的協議進行封裝抽象化的一個接口。我們可以簡單把socket理解成對應的一次tcp連接。 那么網絡操作根本上也是針對網卡的IO操作,我們需要讀取數據/寫入數據,那么如何更加高效地處理數據呢?目前大多數網絡庫都使用IO多路復用機制,在linux系統中最先進的io多路復用就是epoll機制。

epoll工作方式

1642059597458-57fb017e-7e9a-4bcf-b78b-e06c239b3b6c.png

  • 事件通知機制
  • epoll_ctl/epoll_wait
  • ET(邊緣觸發)/LT(水平觸發)
事件通知機制
  • 注冊事件:epoll需要注冊一些可讀的事件
  • 監聽事件:監聽到可讀的數據
  • 觸發事件:通知數據可讀

主要還是有兩個系統調用:

  • epoll_ctl
  • epoll_wait
工作模式

epoll有兩種觸發工作模式:ET和LT

  1. ET也叫邊緣觸發,注冊的事件滿足條件之后,epoll只會觸發一次通知。就算你這一次的讀寫事件的數據沒有處理完,下一次epoll_wait也不會再觸發通知。
  2. LT也叫水平觸發,注冊的事件滿足條件之后,不管數據是否讀寫完成,每一次epoll_wait都會通知當前監聽的fd事件。

BIO/NIO

1642063229291-6b59adf6-3116-483c-ab02-b547b08152dc.png

  1. BIO:blocking I/O,阻塞I/O。就是當我們向一個socket發起read的時候,數據讀取完成之前一直是阻塞的。
  2. NIO:nonblocking I/O,非阻塞I/O。就是read數據的時候不阻塞,立即返回

那么我們每次發現socket中有可讀數據的時候,我們就會開啟一個goroutine讀取數據。

Netpoll的優化點

go的net庫是BIO的,浪費了更多的goroutine在阻塞,並且難以對連接池中的連接進行探活。 netpoll采用了LT的觸發方式,這種觸發方式也就導致編程思路的不同

ET

1642064579869-64deb77c-4845-4103-b735-94a0eb19937b.png

LT

1642064598829-68afa0a4-1e15-4598-aca0-a4b924e1645c.png

netpoll采用LT的編程思路 由於netpoll想在 系統調用 和 buffer 上做優化,所以采用LT的形式。

優化系統調用

syscall這個方法其實有三步:

  1. enter_runtime
  2. raw_syscall
  3. exit_runtime

由於系統調用是一個耗時的阻塞操作,容易造成goroutine阻塞,所以需要加入一些runtime的調度流程。 但是,epoll_wait觸發的事件,保證不會被阻塞,所以netpoll直接采用RawSyscall方法做系統調用,跳過了runtime的一些邏輯。

優化調度

使用msec動態調參和runtime.Gosched主動讓出P

msec動態調參

epoll_wait的系統調用有個參數是,等待時間,設置成-1是無限等待事件到來,0是不等待。

1642066647357-64124af2-f093-4edd-bb41-d7117ce77b4d.png

這樣就有事件到來的時候下次循環的epoll_wait采用立即返回,沒有事件就一直阻塞,減少反復無用的調用。

runtime.Gosched主動讓出P

如果msec為-1的話會立即進入下一次循環,開啟新的epoll_wait調用,那么調用就阻塞在這里,goroutine阻塞時間長了之后會被runtime切換掉,只能等到下一次執行這個goroutine才行,導致時間浪費。 netpoll調用runtime.Gosched方法主動將GMP中的P讓出,減少runtime的調度過程

優化buffer

我們在讀取和寫入數據的時候需要使用到buffer。 多數框架使用環形buffer,可以做到流式讀寫。但是環形buffer容量是死的,需要擴容的話,需要重新copy數組,引入了很多的並發問題。

LinkBuffer

netpoll使用的buffer實現包括:

  • 鏈表解決擴容copy問題
  • sync.Pool復用鏈表節點
  • atomic訪問size,規避data race和鎖競爭

還有一些nocopy方面的優化,減少了write和read的次數,從而提高了讀取和發送的時候的編解碼效率。

更多信息看:https://www.cloudwego.io/zh/blog/2021/10/09/字節跳動在-go-網絡庫上的實踐/#nocopy-buffer


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM