您好,登錄后才能下訂單哦!
這篇文章主要介紹“開源一個輕量級且高性能的Go網(wǎng)絡框架gnet方法是什么”,在日常操作中,相信很多人在開源一個輕量級且高性能的Go網(wǎng)絡框架gnet方法是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”開源一個輕量級且高性能的Go網(wǎng)絡框架gnet方法是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
gnet
是一個基于事件驅(qū)動的高性能和輕量級網(wǎng)絡框架。它直接使用 epoll 和 kqueue 系統(tǒng)調(diào)用而非標準 Golang 網(wǎng)絡包:net 來構(gòu)建網(wǎng)絡應用,它的工作原理類似兩個開源的網(wǎng)絡庫:netty 和 libuv。
這個項目存在的價值是提供一個在網(wǎng)絡包處理方面能和 Redis、Haproxy 這兩個項目具有相近性能的 Go 語言網(wǎng)絡服務器框架。
gnet
的亮點在于它是一個高性能、輕量級、非阻塞的純 Go 實現(xiàn)的傳輸層(TCP/UDP/Unix-Socket)網(wǎng)絡框架,開發(fā)者可以使用 gnet
來實現(xiàn)自己的應用層網(wǎng)絡協(xié)議,從而構(gòu)建出自己的應用層網(wǎng)絡應用:比如在 gnet
上實現(xiàn) HTTP 協(xié)議就可以創(chuàng)建出一個 HTTP 服務器 或者 Web 開發(fā)框架,實現(xiàn) Redis 協(xié)議就可以創(chuàng)建出自己的 Redis 服務器等等。
gnet
衍生自另一個項目:evio
,但性能遠勝之。
高性能 的基于多線程/Go程模型的 event-loop 事件驅(qū)動
內(nèi)置 Round-Robin 輪詢負載均衡算法
內(nèi)置 goroutine 池,由開源庫 ants 提供支持
內(nèi)置 bytes 內(nèi)存池,由開源庫 pool 提供支持
簡潔的 APIs
基于 Ring-Buffer 的高效內(nèi)存利用
支持多種網(wǎng)絡協(xié)議:TCP、UDP、Unix Sockets
支持兩種事件驅(qū)動機制:Linux 里的 epoll 以及 FreeBSD 里的 kqueue
支持異步寫操作
靈活的事件定時器
SO_REUSEPORT 端口重用
gnet
重新設計開發(fā)了一個新內(nèi)置的多線程/Go程模型:『主從多 Reactors』,這也是 netty
默認的線程模型,下面是這個模型的原理圖:
它的運行流程如下面的時序圖:
你可能會問一個問題:如果我的業(yè)務邏輯是阻塞的,那么在 EventHandler.React
注冊方法里的邏輯也會阻塞,從而導致阻塞 event-loop 線程,這時候怎么辦?
正如你所知,基于 gnet
編寫你的網(wǎng)絡服務器有一條最重要的原則:永遠不能讓你業(yè)務邏輯(一般寫在 EventHandler.React
里)阻塞 event-loop 線程,否則的話將會極大地降低服務器的吞吐量,這也是 netty
的一條最重要的原則。
我的回答是,基于gnet
的另一種多線程/Go程模型:『帶線程/Go程池的主從多 Reactors』可以解決阻塞問題,這個新網(wǎng)絡模型通過引入一個 worker pool 來解決業(yè)務邏輯阻塞的問題:它會在啟動的時候初始化一個 worker pool,然后在把 EventHandler.React
里面的阻塞代碼放到 worker pool 里執(zhí)行,從而避免阻塞 event-loop 線程,
模型的架構(gòu)圖如下所示:
它的運行流程如下面的時序圖:
gnet
通過利用 ants goroutine 池(一個基于 Go 開發(fā)的高性能的 goroutine 池 ,實現(xiàn)了對大規(guī)模 goroutines 的調(diào)度管理、goroutines 復用)來實現(xiàn)『主從多 Reactors + 線程/Go程池』網(wǎng)絡模型。關(guān)于 ants
的全部功能和使用,可以在 ants 文檔 里找到。
gnet
內(nèi)部集成了 ants
以及提供了 pool.NewWorkerPool
方法來初始化一個 ants
goroutine 池,然后你可以把 EventHandler.React
中阻塞的業(yè)務邏輯提交到 goroutine 池里執(zhí)行,最后在 goroutine 池里的代碼調(diào)用 gnet.Conn.AsyncWrite
方法把處理完阻塞邏輯之后得到的輸出數(shù)據(jù)異步寫回客戶端,這樣就可以避免阻塞 event-loop 線程。
有關(guān)在 gnet
里使用 ants
goroutine 池的細節(jié)可以到這里進一步了解。
gnet
利用 Ring-Buffer 來緩沖網(wǎng)絡數(shù)據(jù)以及管理內(nèi)存。
$ go get -u github.com/panjf2000/gnet
詳細的文檔在這里: gnet 接口文檔,不過下面我們先來了解下使用 gnet
的簡略方法。
用 gnet
來構(gòu)建網(wǎng)絡服務器是非常簡單的,只需要實現(xiàn) gnet.EventHandler
接口然后把你關(guān)心的事件函數(shù)注冊到里面,最后把它連同監(jiān)聽地址一起傳遞給 gnet.Serve
函數(shù)就完成了。在服務器開始工作之后,每一條到來的網(wǎng)絡連接會在各個事件之間傳遞,如果你想在某個事件中關(guān)閉某條連接或者關(guān)掉整個服務器的話,直接把 gnet.Action
設置成 Cosed
或者 Shutdown
就行了。
Echo 服務器是一種最簡單網(wǎng)絡服務器,把它作為 gnet
的入門例子在再合適不過了,下面是一個最簡單的 echo server,它監(jiān)聽了 9000 端口:
package main import ( "log" "github.com/panjf2000/gnet" ) type echoServer struct { *gnet.EventServer } func (es *echoServer) React(c gnet.Conn) (out []byte, action gnet.Action) { out = c.Read() c.ResetBuffer() return } func main() { echo := new(echoServer) log.Fatal(gnet.Serve(echo, "tcp://:9000", gnet.WithMulticore(true))) }
正如你所見,上面的例子里 gnet
實例只注冊了一個 EventHandler.React
事件。一般來說,主要的業(yè)務邏輯代碼會寫在這個事件方法里,這個方法會在服務器接收到客戶端寫過來的數(shù)據(jù)之時被調(diào)用,然后處理輸入數(shù)據(jù)(這里只是把數(shù)據(jù) echo 回去)并且在處理完之后把需要輸出的數(shù)據(jù)賦值給 out
變量然后返回,之后你就不用管了,gnet
會幫你把數(shù)據(jù)寫回客戶端的。
package main import ( "log" "time" "github.com/panjf2000/gnet" "github.com/panjf2000/gnet/pool" ) type echoServer struct { *gnet.EventServer pool *pool.WorkerPool } func (es *echoServer) React(c gnet.Conn) (out []byte, action gnet.Action) { data := append([]byte{}, c.Read()...) c.ResetBuffer() // Use ants pool to unblock the event-loop. _ = es.pool.Submit(func() { time.Sleep(1 * time.Second) c.AsyncWrite(data) }) return } func main() { p := pool.NewWorkerPool() defer p.Release() echo := &echoServer{pool: p} log.Fatal(gnet.Serve(echo, "tcp://:9000", gnet.WithMulticore(true))) }
正如我在『主從多 Reactors + 線程/Go程池』那一節(jié)所說的那樣,如果你的業(yè)務邏輯里包含阻塞代碼,那么你應該把這些阻塞代碼變成非阻塞的,比如通過把這部分代碼通過 goroutine 去運行,但是要注意一點,如果你的服務器處理的流量足夠的大,那么這種做法將會導致創(chuàng)建大量的 goroutines 極大地消耗系統(tǒng)資源,所以我一般建議你用 goroutine pool 來做 goroutines 的復用和管理,以及節(jié)省系統(tǒng)資源。
更多的例子可以在這里查看: gnet 示例。
gnet
目前支持的 I/O 事件如下:
EventHandler.OnInitComplete
當 server 初始化完成之后調(diào)用。
EventHandler.OnOpened
當連接被打開的時候調(diào)用。
EventHandler.OnClosed
當連接被關(guān)閉的時候調(diào)用。
EventHandler.React
當 server 端接收到從 client 端發(fā)送來的數(shù)據(jù)的時候調(diào)用。(你的核心業(yè)務代碼一般是寫在這個方法里)
EventHandler.Tick
服務器啟動的時候會調(diào)用一次,之后就以給定的時間間隔定時調(diào)用一次,是一個定時器方法。
EventHandler.PreWrite
預先寫數(shù)據(jù)方法,在 server 端寫數(shù)據(jù)回 client 端之前調(diào)用。
EventHandler.Tick
會每隔一段時間觸發(fā)一次,間隔時間你可以自己控制,設定返回的 delay
變量就行。
定時器的第一次觸發(fā)是在 gnet.Serving
事件之后,如果你要設置定時器,別忘了設置 option 選項:WithTicker(true)
。
events.Tick = func() (delay time.Duration, action Action){ log.Printf("tick") delay = time.Second return }
gnet
支持 UDP 協(xié)議,在 gnet.Serve
里綁定 UDP 地址即可,gnet
的 UDP 支持有如下的特性:
數(shù)據(jù)進入服務器之后立刻寫回客戶端,不做緩存。
EventHandler.OnOpened
和 EventHandler.OnClosed
這兩個事件在 UDP 下不可用,唯一可用的事件是 React
。
gnet.WithMulticore(true)
參數(shù)指定了 gnet
是否會使用多核來進行服務,如果是 true
的話就會使用多核,否則就是單核運行,利用的核心數(shù)一般是機器的 CPU 數(shù)量。
gnet
目前內(nèi)置的負載均衡算法是輪詢調(diào)度 Round-Robin,暫時不支持自定制。
服務器支持 SO_REUSEPORT 端口復用特性,允許多個 sockets 監(jiān)聽同一個端口,然后內(nèi)核會幫你做好負載均衡,每次只喚醒一個 socket 來處理 accept 請求,避免驚群效應。
開啟這個功能也很簡單,使用 functional options 設置一下即可:
gnet.Serve(events, "tcp://:9000", gnet.WithMulticore(true), gnet.WithReusePort(true)))
# Machine information OS : Ubuntu 18.04/x86_64 CPU : 8 Virtual CPUs Memory : 16.0 GiB # Go version and configurations Go Version : go1.12.9 linux/amd64 GOMAXPROCS=8
# Machine information OS : macOS Mojave 10.14.6/x86_64 CPU : 4 CPUs Memory : 8.0 GiB # Go version and configurations Go Version : go version go1.12.9 darwin/amd64 GOMAXPROCS=4
到此,關(guān)于“開源一個輕量級且高性能的Go網(wǎng)絡框架gnet方法是什么”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。