成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇學會 Go 網絡庫 Gnet 解析

網絡 網絡優化
本篇文章主要分析gnet的。至于使用姿勢就不發了,gnet有對應的demo庫,可以自行體驗。


開篇

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

但是在一些海量并發連接下,原生netpoll會為每一個連接都開啟一個goroutine處理,也就是1千萬的連接就會創建一千萬個goroutine。

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

本質上他們的底層核心都是一樣的,都是基于epoll(linux)實現的。只是事件發生后,每個庫的處理方式會有所不同。

本篇文章主要分析gnet的。至于使用姿勢就不發了,gnet有對應的demo庫,可以自行體驗。

架構

直接引用gnet官網的一張圖:

圖片

gnet采用的是『主從多 Reactors』。也就是一個主線程負責監聽端口連接,當一個客戶端連接到來時,就把這個連接根據負載均衡算法分配給其中一個sub線程,由對應的sub線程去處理這個連接的讀寫事件以及管理它的死亡。

下面這張圖就更清晰了。

圖片

核心結構

我們先解釋gnet的一些核心結構。

圖片

engine就是程序最上層的結構了。

  • ln對應的listener就是服務啟動后對應監聽端口的監聽器。
  • lb對應的loadBalancer就是負載均衡器。也就是當客戶端連接服務時,負載均衡器會選擇一個sub線程,把連接交給此線程處理。
  • mainLoop 就是我們的主線程了,對應的結構eventloop。當然我們的sub線程結構也是eventloop。結構相同,不同的是職責。主線程負責的是監聽端口發生的客戶端連接事件,然后再由負載均衡器把連接分配給一個sub線程。而sub線程負責的是綁定分配給他的連接(不止一個),且等待自己管理的所有連接后續讀寫事件,并進行處理。

接著看eventloop。

圖片

  • netpoll.Poller:每一個 eventloop都對應一個epoll或者kqueue。
  • buffer用來作為讀消息的緩沖區。
  • connCoun記錄當前eventloop存儲的tcp連接數。
  • udpSockets和connetcions分別管理著這個eventloop下所有的udp socket和tcp連接,注意他們的結構map。這里的int類型存儲的就是fd。

對應conn結構。

圖片

這里面有幾個字段介紹下:

  • buffer:存儲當前conn對端(client)發送的最新數據,比如發送了三次,那個此時buffer存儲的是第三次的數據,代碼里有。
  • inboundBuffer:存儲對端發送的且未被用戶讀取的剩余數據,還是個Ring Buffer。
  • outboundBuffer:存儲還未發送給對端的數據。(比如服務端響應客戶端的數據,由于conn fd是不阻塞的,調用write返回不可寫的時候,就可以先把數據放到這里)

conn相當于每個連接都會有自己獨立的緩存空間。這樣做是為了減少集中式管理內存帶來的鎖問題。使用Ring buffer是為了增加空間的復用性。

整體結構就這些。

核心邏輯

當程序啟動時,

圖片

會根據用戶設置的options明確eventloop循環的數量,也就是有多少個sub線程。再進一步說,在linux環境就是會創建多少個epoll對象。

那么整個程序的epoll對象數量就是count(sub)+1(main Listener)。

圖片

上圖就是我說的,會根據設置的數量創建對應的eventloop,把對應的eventloop 注冊到負載均衡器中。

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

我們先來看主線程,(由于我使用的是mac,所以后面關于IO多路復用,實現部分就是kqueue代碼了,當然原理是一樣的)。

圖片

Polling就是等待網絡事件到來,傳遞了一個閉包參數,更確切的說是一個事件到來時的回調函數,從名字可以看出,就是處理新連接的。

至于Polling函數。

圖片

邏輯很簡單,一個for循環等待事件到來,然后處理事件。

主線程的事件分兩種:

一種是正常的fd發生網絡連接事件。

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

圖片

通過NOTE_TRIGGER觸發告訴你隊列里有task任務,去執行task任務。

如果是正常的網絡事件到來,就處理閉包函數,主線程處理的就是上面的accept連接函數。

圖片

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

圖片

register做了兩件事,首先需要把當前連接注冊到當前sub 線程的epoll or kqueue 對象中,新增read的flag。

接著就是把當前連接放入到connections的map結構中 fd->conn。

這樣當對應的sub線程事件到來時,可以通過事件的fd找到是哪個連接,進行相應的處理。

圖片

如果是可讀事件。

圖片

到這里分析差不多就結束了。

總結

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

那是因為事件到來時,采取的都是非阻塞的操作,且是串行處理對應的每個fd(conn)。每個conn操作的都是自身持有的緩存空間。同時處理完一輪觸發的所有事件才會循環進入下一次等待,在此層面上解決了并發問題。

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

圖片

而應該先拿到數據,再異步處理。

圖片

issues上有提到,連接是使用map[int]*conn存儲的。gnet本身的場景就是海量并發連接,內存會很大。進而big map存指針會對 GC造成很大的負擔,畢竟它不像數組一樣,是連續內存空間,易于GC掃描。

還有一點,在處理buffer數據的時候,就像上面看到的,本質上是將buffer數據copy給用戶一份,那么就存在大量copy開銷,在這一點上,字節的netpoll實現了Nocopy Buffer,改天研究一下。

責任編輯:武曉燕 來源: RememberGo
相關推薦

2021-07-16 22:43:10

Go并發Golang

2021-07-02 08:51:29

源碼參數Thread

2022-05-17 08:02:55

GoTryLock模式

2023-12-05 07:14:27

AIGo

2021-11-15 10:29:39

Go語言類型

2022-01-02 08:43:46

Python

2024-05-10 08:15:32

go語言反射機制

2022-09-23 07:15:22

docker網絡Liunx

2022-02-07 11:01:23

ZooKeeper

2021-05-11 08:54:59

建造者模式設計

2021-07-02 09:45:29

MySQL InnoDB數據

2021-07-06 08:59:18

抽象工廠模式

2023-01-03 08:31:54

Spring讀取器配置

2022-08-26 09:29:01

Kubernetes策略Master

2021-07-05 22:11:38

MySQL體系架構

2023-11-28 08:29:31

Rust內存布局

2022-08-23 08:00:59

磁盤性能網絡

2021-09-28 08:59:30

復原IP地址

2021-10-14 10:22:19

逃逸JVM性能

2022-04-12 08:30:52

回調函數代碼調試
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 偷拍自拍网站 | 91亚洲精品在线观看 | 精品国产一二三区 | 成人国产精品久久 | 日韩有码在线观看 | 男人的天堂在线视频 | 久久精品久久精品 | 精品一二区 | 亚洲国产精品第一区二区 | 国产精品亚洲精品 | 麻豆va| 黄视频欧美 | 国产精品免费一区二区三区 | 午夜视频一区 | 日韩精品一区二区三区视频播放 | 国产一区二区三区 | 天天插天天操 | 综合久久一区 | 东方伊人免费在线观看 | 中文字幕1区 | 91国内精精品久久久久久婷婷 | 国产高清一区二区三区 | 91久久| 91久久精品一区二区二区 | 视频一区欧美 | 久久神马 | 在线亚洲一区 | 成人在线免费观看 | 丁香五月网久久综合 | 亚洲电影一区二区三区 | 7777在线视频免费播放 | 精品成人一区二区 | 91传媒在线观看 | 久久精品在线免费视频 | 午夜一区| 日韩欧美三区 | 欧美一级欧美三级在线观看 | 国产精品久久久亚洲 | 中文字幕av一区 | 日韩精品一区二区三区四区视频 | av色站|