長(zhǎng)連接角度netty+java和libevent+c的比較

引子

現(xiàn)在在公司的一項(xiàng)工作是負(fù)責(zé)IM系統(tǒng)的長(zhǎng)連接,我們的長(zhǎng)連接系統(tǒng)是用C實(shí)現(xiàn)的,事件驅(qū)動(dòng)使用的是libevent,有一次和另一個(gè)朋友交流,他們說(shuō)他們的長(zhǎng)連接是基于netty實(shí)現(xiàn)的,實(shí)現(xiàn)起來(lái)比我們的要簡(jiǎn)單方便很多,當(dāng)時(shí)就想后面有時(shí)間就比較一下這兩種實(shí)現(xiàn)方案。

其實(shí)這倆本來(lái)是沒(méi)有可比性的,所以本文對(duì)比的是netty+java實(shí)現(xiàn)的長(zhǎng)連接和libevent+c實(shí)現(xiàn)的長(zhǎng)連接系統(tǒng)的對(duì)比。

長(zhǎng)連接系統(tǒng)簡(jiǎn)介

IM的長(zhǎng)連接是指用戶端和后臺(tái)建立的一條較長(zhǎng)時(shí)間存在的信息通路,起到的最大作用是消息的實(shí)時(shí)觸達(dá)。當(dāng)后臺(tái)有消息的時(shí)候,會(huì)通過(guò)這個(gè)通路實(shí)時(shí)的發(fā)送給用戶。

我們的用戶端有app端,js端,pc端,為了能兼容所有的設(shè)備和瀏覽器,我們初始的協(xié)議選擇了http協(xié)議,使用的是long polling的方式,后來(lái)協(xié)議增加了websocket協(xié)議,在高級(jí)別瀏覽器和微信小程序能提供更好的體驗(yàn)。

內(nèi)存對(duì)比

基于libevent+C實(shí)現(xiàn)

基本數(shù)據(jù)結(jié)構(gòu)

  • session數(shù)組
  • 每個(gè)鏈接的動(dòng)態(tài)buffer

session數(shù)組的索引是fd,存儲(chǔ)的是session對(duì)象,session對(duì)象主要包括一個(gè)鏈接的客戶端ip,登錄時(shí)間,讀和寫(xiě)的buffer指針,讀和寫(xiě)event以及創(chuàng)建時(shí)間等。每個(gè)session對(duì)象416B,由于使用的是數(shù)組,我們根據(jù)系統(tǒng)的最大承受量,預(yù)留了100w個(gè)session對(duì)象,所以占用空間416M。

每個(gè)鏈接的讀buffer是為了解決tcp的粘包問(wèn)題,即讀到一個(gè)完整的業(yè)務(wù)包才能交到下一個(gè)環(huán)節(jié)處理。
每個(gè)鏈接的寫(xiě)buffer是為了解決tcp的鏈接不可寫(xiě)的問(wèn)題,因?yàn)槭褂玫氖钱惒讲僮?,有?shù)據(jù)的時(shí)候,有可能tcp鏈接并不可寫(xiě),這時(shí)候就緩存在寫(xiě)buffer里面,可寫(xiě)的時(shí)候再寫(xiě)。

基于netty+java實(shí)現(xiàn)

netty中一個(gè)鏈接是一個(gè)channel,每一個(gè)channel會(huì)有一個(gè)DefaultPipeline,然后會(huì)有一個(gè)HeadContext和TailContext以及Unsafe對(duì)象。java對(duì)象比較多,所以java占用的內(nèi)存會(huì)多一些。

netty實(shí)現(xiàn)的長(zhǎng)連接的內(nèi)存會(huì)有一個(gè)gc的問(wèn)題,因?yàn)殚L(zhǎng)連接的鏈接相關(guān)對(duì)象的存活時(shí)間是和鏈接相同聲明周期的,那么會(huì)有很多內(nèi)存存儲(chǔ)在老年代,會(huì)導(dǎo)致fgc的時(shí)間比較長(zhǎng)。

線程管理(CPU)對(duì)比

基于libevent+C實(shí)現(xiàn)

因?yàn)槲覀兪褂玫氖羌儺惒降脑O(shè)計(jì),所以我們首先申請(qǐng)了和cpu核數(shù)一樣多的線程數(shù),然后通過(guò)fd取模得到對(duì)應(yīng)的線程索引,那么這個(gè)鏈接的所有讀寫(xiě)都是這個(gè)線程負(fù)責(zé)的。(accept的線程后面會(huì)單說(shuō))

基于netty+java實(shí)現(xiàn)

netty的EventLoopGroup的一個(gè)實(shí)現(xiàn)是MultithreadEventLoopGroup,這個(gè)里面使用多個(gè)線程,每一個(gè)線程都是一個(gè)NioEventLoop,channel選擇EventLoop的方式是輪詢,所以每一個(gè)channel里有一個(gè)引用指向這個(gè)NioEventLoop,這個(gè)channel的所有讀寫(xiě)都是這個(gè)NioEventLoop完成的(包括accept的線程)。

線程交互

線程交互指的是一個(gè)鏈接的要給另一個(gè)鏈接發(fā)送數(shù)據(jù),而這兩個(gè)鏈接又有可能屬于不同的線程,所以存在線程交互的問(wèn)題。

基于libevent+C實(shí)現(xiàn)

libevent是基于事件驅(qū)動(dòng)的,所以我們引入了pipe,線程之間交互是通過(guò)pipe的讀事件通知的,當(dāng)一個(gè)線程管理的鏈接需要給另一個(gè)線程管理的鏈接發(fā)送數(shù)據(jù)的時(shí)候,就通過(guò)pipe通知另一個(gè)線程。pipe有一個(gè)比較好的特性是write接口原子性,即發(fā)送4096的數(shù)據(jù)是原子的,就是多個(gè)線程同時(shí)寫(xiě)的時(shí)候不會(huì)出現(xiàn)一個(gè)線程寫(xiě)一半數(shù)據(jù),然后另一個(gè)線程寫(xiě)一半數(shù)據(jù)的情況。

基于netty+java的實(shí)現(xiàn)

netty的實(shí)現(xiàn)沒(méi)有使用事件的機(jī)制,而是加入了一個(gè)隊(duì)列,同一個(gè)線程在處理完nio的事件后會(huì)檢查一下隊(duì)列是否有數(shù)據(jù)要發(fā),因?yàn)閚io在沒(méi)有數(shù)據(jù)的時(shí)候是會(huì)block的,所以需要wakeup一下。

accept線程優(yōu)化

長(zhǎng)連接是一個(gè)外網(wǎng)的系統(tǒng),那么不能避免的會(huì)有攻擊或者垃圾流量過(guò)來(lái),所以有可能鏈接的請(qǐng)求特別多,在我們的系統(tǒng)里面,accept的線程的cpu占用是其他線程的2倍。

基于libevent+C的優(yōu)化

給同一個(gè)fd(accept socket的fd)分配多個(gè)event,然后綁定到多個(gè)線程,這樣cpu就比較均勻了,缺點(diǎn)是:同一個(gè)event會(huì)有多個(gè)線程同時(shí)相應(yīng),但只有一個(gè)socket能accept到結(jié)果。

nginx的優(yōu)化

nginx的accept的fd不是固定到一個(gè)線程管理的,哪個(gè)線程空閑就那個(gè)線程管理,這樣也能使cpu比較均勻。

實(shí)現(xiàn)復(fù)雜度

上面提到了內(nèi)存管理(分為動(dòng)態(tài)和靜態(tài)兩部分)、線程分布、線程交互和accept優(yōu)化四個(gè)方面,netty幾乎都已經(jīng)比較好的解決了,所以如果基于netty和java實(shí)現(xiàn)的話,實(shí)現(xiàn)復(fù)雜程度就會(huì)簡(jiǎn)單很多,如果用libevent和c來(lái)實(shí)現(xiàn),都需要自己來(lái)實(shí)現(xiàn),實(shí)現(xiàn)復(fù)雜程度陡增。

總結(jié)

上面從內(nèi)存、CPU、線程交互、accpet線程優(yōu)化以及實(shí)現(xiàn)復(fù)雜度五個(gè)方面比較了基于netty和基于libevent+c的實(shí)現(xiàn),總結(jié)起來(lái):

  • 內(nèi)存上c有優(yōu)勢(shì),特別是長(zhǎng)連接這樣的系統(tǒng),大部分內(nèi)存都會(huì)進(jìn)入java的老年代
  • accept線程優(yōu)化方面,netty并沒(méi)有很好的考慮
  • 實(shí)現(xiàn)復(fù)雜程度,netty+java的方案簡(jiǎn)單很多,對(duì)并發(fā)連接數(shù)不是很多的系統(tǒng),可以優(yōu)先考慮
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容