自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇學(xué)會(huì) Go 網(wǎng)絡(luò)庫(kù) Gnet 解析

網(wǎng)絡(luò) 網(wǎng)絡(luò)優(yōu)化
本篇文章主要分析gnet的。至于使用姿勢(shì)就不發(fā)了,gnet有對(duì)應(yīng)的demo庫(kù),可以自行體驗(yàn)。


開篇

我們分析了Go原生網(wǎng)絡(luò)模型以及部分源碼,絕大部分場(chǎng)景下(99%),使用原生netpoll已經(jīng)足夠了。

但是在一些海量并發(fā)連接下,原生netpoll會(huì)為每一個(gè)連接都開啟一個(gè)goroutine處理,也就是1千萬的連接就會(huì)創(chuàng)建一千萬個(gè)goroutine。

這就給了這些特殊場(chǎng)景下的優(yōu)化空間,這也是像gnet和cloudwego/netpoll誕生的原因之一吧。

本質(zhì)上他們的底層核心都是一樣的,都是基于epoll(linux)實(shí)現(xiàn)的。只是事件發(fā)生后,每個(gè)庫(kù)的處理方式會(huì)有所不同。

本篇文章主要分析gnet的。至于使用姿勢(shì)就不發(fā)了,gnet有對(duì)應(yīng)的demo庫(kù),可以自行體驗(yàn)。

架構(gòu)

直接引用gnet官網(wǎng)的一張圖:

圖片

gnet采用的是『主從多 Reactors』。也就是一個(gè)主線程負(fù)責(zé)監(jiān)聽端口連接,當(dāng)一個(gè)客戶端連接到來時(shí),就把這個(gè)連接根據(jù)負(fù)載均衡算法分配給其中一個(gè)sub線程,由對(duì)應(yīng)的sub線程去處理這個(gè)連接的讀寫事件以及管理它的死亡。

下面這張圖就更清晰了。

圖片

核心結(jié)構(gòu)

我們先解釋gnet的一些核心結(jié)構(gòu)。

圖片

engine就是程序最上層的結(jié)構(gòu)了。

  • ln對(duì)應(yīng)的listener就是服務(wù)啟動(dòng)后對(duì)應(yīng)監(jiān)聽端口的監(jiān)聽器。
  • lb對(duì)應(yīng)的loadBalancer就是負(fù)載均衡器。也就是當(dāng)客戶端連接服務(wù)時(shí),負(fù)載均衡器會(huì)選擇一個(gè)sub線程,把連接交給此線程處理。
  • mainLoop 就是我們的主線程了,對(duì)應(yīng)的結(jié)構(gòu)eventloop。當(dāng)然我們的sub線程結(jié)構(gòu)也是eventloop。結(jié)構(gòu)相同,不同的是職責(zé)。主線程負(fù)責(zé)的是監(jiān)聽端口發(fā)生的客戶端連接事件,然后再由負(fù)載均衡器把連接分配給一個(gè)sub線程。而sub線程負(fù)責(zé)的是綁定分配給他的連接(不止一個(gè)),且等待自己管理的所有連接后續(xù)讀寫事件,并進(jìn)行處理。

接著看eventloop。

圖片

  • netpoll.Poller:每一個(gè) eventloop都對(duì)應(yīng)一個(gè)epoll或者kqueue。
  • buffer用來作為讀消息的緩沖區(qū)。
  • connCoun記錄當(dāng)前eventloop存儲(chǔ)的tcp連接數(shù)。
  • udpSockets和connetcions分別管理著這個(gè)eventloop下所有的udp socket和tcp連接,注意他們的結(jié)構(gòu)map。這里的int類型存儲(chǔ)的就是fd。

對(duì)應(yīng)conn結(jié)構(gòu)。

圖片

這里面有幾個(gè)字段介紹下:

  • buffer:存儲(chǔ)當(dāng)前conn對(duì)端(client)發(fā)送的最新數(shù)據(jù),比如發(fā)送了三次,那個(gè)此時(shí)buffer存儲(chǔ)的是第三次的數(shù)據(jù),代碼里有。
  • inboundBuffer:存儲(chǔ)對(duì)端發(fā)送的且未被用戶讀取的剩余數(shù)據(jù),還是個(gè)Ring Buffer。
  • outboundBuffer:存儲(chǔ)還未發(fā)送給對(duì)端的數(shù)據(jù)。(比如服務(wù)端響應(yīng)客戶端的數(shù)據(jù),由于conn fd是不阻塞的,調(diào)用write返回不可寫的時(shí)候,就可以先把數(shù)據(jù)放到這里)

conn相當(dāng)于每個(gè)連接都會(huì)有自己獨(dú)立的緩存空間。這樣做是為了減少集中式管理內(nèi)存帶來的鎖問題。使用Ring buffer是為了增加空間的復(fù)用性。

整體結(jié)構(gòu)就這些。

核心邏輯

當(dāng)程序啟動(dòng)時(shí),

圖片

會(huì)根據(jù)用戶設(shè)置的options明確eventloop循環(huán)的數(shù)量,也就是有多少個(gè)sub線程。再進(jìn)一步說,在linux環(huán)境就是會(huì)創(chuàng)建多少個(gè)epoll對(duì)象。

那么整個(gè)程序的epoll對(duì)象數(shù)量就是count(sub)+1(main Listener)。

圖片

上圖就是我說的,會(huì)根據(jù)設(shè)置的數(shù)量創(chuàng)建對(duì)應(yīng)的eventloop,把對(duì)應(yīng)的eventloop 注冊(cè)到負(fù)載均衡器中。

當(dāng)新連接到來時(shí),就可以根據(jù)一定的算法(gnet提供了輪詢、最少連接以及hash)挑選其中一個(gè)eventloop把連接分配給它。

我們先來看主線程,(由于我使用的是mac,所以后面關(guān)于IO多路復(fù)用,實(shí)現(xiàn)部分就是kqueue代碼了,當(dāng)然原理是一樣的)。

圖片

Polling就是等待網(wǎng)絡(luò)事件到來,傳遞了一個(gè)閉包參數(shù),更確切的說是一個(gè)事件到來時(shí)的回調(diào)函數(shù),從名字可以看出,就是處理新連接的。

至于Polling函數(shù)。

圖片

邏輯很簡(jiǎn)單,一個(gè)for循環(huán)等待事件到來,然后處理事件。

主線程的事件分兩種:

一種是正常的fd發(fā)生網(wǎng)絡(luò)連接事件。

一種是通過NOTE_TRIGGER立即激活的事件。

圖片

通過NOTE_TRIGGER觸發(fā)告訴你隊(duì)列里有task任務(wù),去執(zhí)行task任務(wù)。

如果是正常的網(wǎng)絡(luò)事件到來,就處理閉包函數(shù),主線程處理的就是上面的accept連接函數(shù)。

圖片

accept連接邏輯很簡(jiǎn)單,拿到連接的fd。設(shè)置fd非阻塞模式(想想連接是阻塞的會(huì)咋么樣?),然后根據(jù)負(fù)載均衡算法選擇一個(gè)sub 線程,通過register函數(shù)把此連接分配給它。

圖片

register做了兩件事,首先需要把當(dāng)前連接注冊(cè)到當(dāng)前sub 線程的epoll or kqueue 對(duì)象中,新增read的flag。

接著就是把當(dāng)前連接放入到connections的map結(jié)構(gòu)中 fd->conn。

這樣當(dāng)對(duì)應(yīng)的sub線程事件到來時(shí),可以通過事件的fd找到是哪個(gè)連接,進(jìn)行相應(yīng)的處理。

圖片

如果是可讀事件。

圖片

到這里分析差不多就結(jié)束了。

總結(jié)

在gnet里面,你可以看到,基本上所有的操作都無鎖的。

那是因?yàn)槭录絹頃r(shí),采取的都是非阻塞的操作,且是串行處理對(duì)應(yīng)的每個(gè)fd(conn)。每個(gè)conn操作的都是自身持有的緩存空間。同時(shí)處理完一輪觸發(fā)的所有事件才會(huì)循環(huán)進(jìn)入下一次等待,在此層面上解決了并發(fā)問題。

當(dāng)然這樣用戶在使用的時(shí)候也需要注意一些問題,比如用戶在自定義EventHandler中,如果要異步處理邏輯,就不能像下面這樣開一個(gè)g然后在里面獲取本次數(shù)據(jù)。

圖片

而應(yīng)該先拿到數(shù)據(jù),再異步處理。

圖片

issues上有提到,連接是使用map[int]*conn存儲(chǔ)的。gnet本身的場(chǎng)景就是海量并發(fā)連接,內(nèi)存會(huì)很大。進(jìn)而big map存指針會(huì)對(duì) GC造成很大的負(fù)擔(dān),畢竟它不像數(shù)組一樣,是連續(xù)內(nèi)存空間,易于GC掃描。

還有一點(diǎn),在處理buffer數(shù)據(jù)的時(shí)候,就像上面看到的,本質(zhì)上是將buffer數(shù)據(jù)copy給用戶一份,那么就存在大量copy開銷,在這一點(diǎn)上,字節(jié)的netpoll實(shí)現(xiàn)了Nocopy Buffer,改天研究一下。

責(zé)任編輯:武曉燕 來源: RememberGo
相關(guān)推薦

2021-07-16 22:43:10

Go并發(fā)Golang

2021-07-02 08:51:29

源碼參數(shù)Thread

2022-05-17 08:02:55

GoTryLock模式

2023-12-05 07:14:27

AIGo

2021-11-15 10:29:39

Go語言類型

2022-09-23 07:15:22

docker網(wǎng)絡(luò)Liunx

2024-05-10 08:15:32

go語言反射機(jī)制

2022-01-02 08:43:46

Python

2022-02-07 11:01:23

ZooKeeper

2023-01-03 08:31:54

Spring讀取器配置

2021-05-11 08:54:59

建造者模式設(shè)計(jì)

2021-07-05 22:11:38

MySQL體系架構(gòu)

2021-07-06 08:59:18

抽象工廠模式

2022-08-26 09:29:01

Kubernetes策略Master

2023-11-28 08:29:31

Rust內(nèi)存布局

2021-07-02 09:45:29

MySQL InnoDB數(shù)據(jù)

2022-08-23 08:00:59

磁盤性能網(wǎng)絡(luò)

2021-10-27 09:59:35

存儲(chǔ)

2021-09-28 08:59:30

復(fù)原IP地址

2022-04-12 08:30:52

回調(diào)函數(shù)代碼調(diào)試
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)