深入理解Netty

ByteBuf

ByteBuf是一個(gè)節(jié)點(diǎn)容器,里面數(shù)據(jù)包括三部分:
1侄刽、已經(jīng)丟棄的數(shù)據(jù)衷模,這部分?jǐn)?shù)據(jù)是無效的
2鹊汛、可讀字節(jié),這部分?jǐn)?shù)據(jù)是ByteBuf的主體
3阱冶、可寫字節(jié)
這三段數(shù)據(jù)被兩個(gè)指針給劃分出來刁憋,讀指針、寫指針木蹬。

總結(jié)
1至耻、 ByteBuf 本質(zhì)上就是,它引用了一段內(nèi)存镊叁,這段內(nèi)存可以是堆內(nèi)也可以是堆外的尘颓,然后用引用計(jì)數(shù)來控制這段內(nèi)存是否需要被釋放,使用讀寫指針來控制對(duì) ByteBuf 的讀寫晦譬,可以理解為是外觀模式的一種使用
2疤苹、基于讀寫指針和容量、最大可擴(kuò)容容量敛腌,衍生出一系列的讀寫方法卧土,要注意 read/write 與 get/set 的區(qū)別
3、多個(gè) ByteBuf 可以引用同一段內(nèi)存像樊,通過引用計(jì)數(shù)來控制內(nèi)存的釋放尤莺,遵循誰 retain() 誰 release() 的原則

通信協(xié)議編解碼

1、客戶端把一個(gè)Java對(duì)象按照通信協(xié)議轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)包
2生棍、把二進(jìn)制數(shù)據(jù)包發(fā)送到服務(wù)端缝裁,數(shù)據(jù)的傳輸油TCP/IP協(xié)議負(fù)責(zé)
3、服務(wù)端收到二進(jìn)制數(shù)據(jù)包后足绅,按照通信協(xié)議,包裝成Java對(duì)象韩脑。

通信協(xié)議的設(shè)計(jì)
1氢妈、魔數(shù),作用:能夠在第一時(shí)間識(shí)別出這個(gè)數(shù)據(jù)包是不是遵循自定義協(xié)議的段多,也就是無效數(shù)據(jù)包首量,為了安全考慮可以直接關(guān)閉連接以節(jié)省資源。
2、版本號(hào)加缘,
3鸭叙、序列化算法
4、指令
5拣宏、數(shù)據(jù)長度
6沈贝、數(shù)據(jù)

總結(jié)
1、通信協(xié)議是為了服務(wù)端與客戶端交互勋乾,雙方協(xié)商出來的滿足一定規(guī)則的二進(jìn)制格式
2宋下、介紹了一種通用的通信協(xié)議的設(shè)計(jì),包括魔數(shù)辑莫、版本號(hào)学歧、序列化算法標(biāo)識(shí)、指令各吨、數(shù)據(jù)長度枝笨、數(shù)據(jù)幾個(gè)字段,該協(xié)議能夠滿足絕大多數(shù)的通信場(chǎng)景揭蜒。

客戶端登陸

1横浑、客戶端會(huì)構(gòu)建一個(gè)登錄請(qǐng)求對(duì)象,然后通過編碼把請(qǐng)求對(duì)象編碼為 ByteBuf忌锯,寫到服務(wù)端伪嫁。
2、服務(wù)端接受到 ByteBuf 之后偶垮,首先通過解碼把 ByteBuf 解碼為登錄請(qǐng)求響應(yīng)张咳,然后進(jìn)行校驗(yàn)。
3似舵、服務(wù)端校驗(yàn)通過之后脚猾,構(gòu)造一個(gè)登錄響應(yīng)對(duì)象,依然經(jīng)過編碼砚哗,然后再寫回到客戶端龙助。
4、客戶端接收到服務(wù)端的之后蛛芥,解碼 ByteBuf提鸟,拿到登錄響應(yīng)響應(yīng),判斷是否登陸成功仅淑。

客戶端和服務(wù)端收發(fā)信息

判斷客戶端是否登陸成功
1称勋、通過 channel.attr(xxx).set(xx) 的方式,我們可以在登陸成功后涯竟,給Channel綁定一個(gè)登陸成功的標(biāo)志位赡鲜,判斷登陸的時(shí)候取出這個(gè)標(biāo)志位就可以了

pipeline與channelHandler

它們通過責(zé)任鏈設(shè)計(jì)模式來組織代碼邏輯空厌,并且支持邏輯的動(dòng)態(tài)添加和刪除。

ChannelHandler 有兩大子接口:
第一個(gè)子接口是 ChannelInboundHandler银酬,從字面意思也可以猜到嘲更,他是處理讀數(shù)據(jù)的邏輯
第二個(gè)子接口 ChannelOutBoundHandler 是處理寫數(shù)據(jù)的邏輯

這兩個(gè)子接口分別有對(duì)應(yīng)的默認(rèn)實(shí)現(xiàn),ChannelInboundHandlerAdapter揩瞪,和 ChanneloutBoundHandlerAdapter赋朦,它們分別實(shí)現(xiàn)了兩大接口的所有功能,默認(rèn)情況下會(huì)把讀寫事件傳播到下一個(gè) handler壮韭。

總結(jié)
1北发、通過我們前面編寫客戶端服務(wù)端處理邏輯,引出了 pipeline 和 channelHandler 的概念喷屋。
2琳拨、channelHandler 分為 inBound 和 outBound 兩種類型的接口,分別是處理數(shù)據(jù)讀與數(shù)據(jù)寫的邏輯屯曹,可與 tcp 協(xié)議棧聯(lián)系起來狱庇。
3、兩種類型的 handler 均有相應(yīng)的默認(rèn)實(shí)現(xiàn)恶耽,默認(rèn)會(huì)把事件傳遞到下一個(gè)密任,這里的傳遞事件其實(shí)說白了就是把本 handler 的處理結(jié)果傳遞到下一個(gè) handler 繼續(xù)處理。
4偷俭、inBoundHandler 的執(zhí)行順序與我們實(shí)際的添加順序相同浪讳,而 outBoundHandler 則相反。

拆包粘包理論與解決方案

TCP是個(gè)“流”協(xié)議涌萤,所謂流淹遵,就是沒有界限的一串?dāng)?shù)據(jù)。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義负溪,它會(huì)根據(jù)TCP緩沖區(qū)的實(shí)際情況進(jìn)行包的劃分透揣,所以在業(yè)務(wù)上認(rèn)為,一個(gè)完整的包可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送川抡,也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送辐真,這就是所謂的TCP粘包和拆包的問題。

解決方法
1崖堤、解決思路是在封裝自己的包協(xié)議:包=包內(nèi)容長度(4byte)+包內(nèi)容
2侍咱、對(duì)于粘包問題先讀出包頭即包體長度n,然后再讀取長度為n的包內(nèi)容密幔,這樣數(shù)據(jù)包之間的邊界就清楚了放坏。
3、對(duì)于斷包問題先讀出包頭即包體長度n老玛,由于此次讀取的緩存區(qū)長度小于n,這時(shí)候就需要先緩存這部分的內(nèi)容,等待下次read事件來時(shí)拼接起來形成完整的數(shù)據(jù)包蜡豹。

總結(jié)
1麸粮、拆包器的作用就是根據(jù)我們的自定義協(xié)議,把數(shù)據(jù)拼裝成一個(gè)個(gè)符合我們自定義數(shù)據(jù)包大小的 ByteBuf镜廉,然后送到我們的自定義協(xié)議解碼器去解碼弄诲。
2、Netty 自帶的拆包器包括基于固定長度的拆包器娇唯,基于換行符和自定義分隔符的拆包器齐遵,還有另外一種最重要的基于長度域的拆包器。通常 Netty 自帶的拆包器已完全滿足我們的需求塔插,無需重復(fù)造輪子梗摇。
3、基于 Netty 自帶的拆包器想许,我們可以在拆包之前判斷當(dāng)前連上來的客戶端是否是支持自定義協(xié)議的客戶端伶授,如果不支持,可盡早關(guān)閉流纹,節(jié)省資源糜烹。

Netty 自帶的拆包器

  1. 固定長度的拆包器 FixedLengthFrameDecoder
    如果你的應(yīng)用層協(xié)議非常簡(jiǎn)單,每個(gè)數(shù)據(jù)包的長度都是固定的漱凝,比如 100疮蹦,那么只需要把這個(gè)拆包器加到 pipeline 中,Netty 會(huì)把一個(gè)個(gè)長度為 100 的數(shù)據(jù)包 (ByteBuf) 傳遞到下一個(gè) channelHandler茸炒。

  2. 行拆包器 LineBasedFrameDecoder
    從字面意思來看愕乎,發(fā)送端發(fā)送數(shù)據(jù)包的時(shí)候,每個(gè)數(shù)據(jù)包之間以換行符作為分隔扣典,接收端通過 LineBasedFrameDecoder 將粘過的 ByteBuf 拆分成一個(gè)個(gè)完整的應(yīng)用層數(shù)據(jù)包妆毕。

  3. 分隔符拆包器 DelimiterBasedFrameDecoder
    DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不過我們可以自定義分隔符贮尖。

  4. 基于長度域拆包器 LengthFieldBasedFrameDecoder
    最后一種拆包器是最通用的一種拆包器笛粘,只要你的自定義協(xié)議中包含長度域字段,均可以使用這個(gè)拆包器來實(shí)現(xiàn)應(yīng)用層拆包湿硝。由于上面三種拆包器比較簡(jiǎn)單薪前,讀者可以自行寫出 demo,接下來关斜,我們就結(jié)合我們小冊(cè)的自定義協(xié)議示括,來學(xué)習(xí)一下如何使用基于長度域的拆包器來拆解我們的數(shù)據(jù)包。

channelHandler 生命周期

ChannelHandler 回調(diào)方法的執(zhí)行順序?yàn)?/p>

handlerAdded() -> channelRegistered() -> channelActive() -> channelRead() -> channelReadComplete()
我們來逐個(gè)解釋一下每個(gè)回調(diào)方法的含義
1痢畜、handlerAdded() :指的是當(dāng)檢測(cè)到新連接之后垛膝,調(diào)用 ch.pipeline().addLast(new LifeCyCleTestHandler()); 之后的回調(diào)鳍侣,表示在當(dāng)前的 channel 中,已經(jīng)成功添加了一個(gè) handler 處理器吼拥。
2倚聚、channelRegistered():這個(gè)回調(diào)方法,表示當(dāng)前的 channel 的所有的邏輯處理已經(jīng)和某個(gè) NIO 線程建立了綁定關(guān)系凿可,類似我們?cè)贜etty 是什么惑折?這小節(jié)中 BIO 編程中,accept 到新的連接枯跑,然后創(chuàng)建一個(gè)線程來處理這條連接的讀寫惨驶,只不過 Netty 里面是使用了線程池的方式,只需要從線程池里面去抓一個(gè)線程綁定在這個(gè) channel 上即可敛助,這里的 NIO 線程通常指的是 NioEventLoop,不理解沒關(guān)系粗卜,后面我們還會(huì)講到。
3辜腺、channelActive():當(dāng) channel 的所有的業(yè)務(wù)邏輯鏈準(zhǔn)備完畢(也就是說 channel 的 pipeline 中已經(jīng)添加完所有的 handler)以及綁定好一個(gè) NIO 線程之后休建,這條連接算是真正激活了,接下來就會(huì)回調(diào)到此方法评疗。
4测砂、channelRead():客戶端向服務(wù)端發(fā)來數(shù)據(jù),每次都會(huì)回調(diào)此方法百匆,表示有數(shù)據(jù)可讀砌些。
5、channelReadComplete():服務(wù)端每次讀完一次完整的數(shù)據(jù)之后加匈,回調(diào)該方法存璃,表示數(shù)據(jù)讀取完畢。

單聊

1雕拼、A 要和 B 聊天纵东,首先 A 和 B 需要與服務(wù)器建立連接,然后進(jìn)行一次登錄流程啥寇,服務(wù)端保存用戶標(biāo)識(shí)和 TCP 連接的映射關(guān)系偎球。
2、A 發(fā)消息給 B辑甜,首先需要將帶有 B 標(biāo)識(shí)的消息數(shù)據(jù)包發(fā)送到服務(wù)器衰絮,然后服務(wù)器從消息數(shù)據(jù)包中拿到 B 的標(biāo)識(shí),找到對(duì)應(yīng)的 B 的連接磷醋,將消息發(fā)送給 B猫牡。

總結(jié):
1、我們定義一個(gè)會(huì)話類 Session 用戶維持用戶的登錄信息邓线,用戶登錄的時(shí)候綁定 Session 與 channel淌友,用戶登出或者斷線的時(shí)候解綁 Session 與 channel煌恢。
2、服務(wù)端處理消息的時(shí)候亩进,通過消息接收方的標(biāo)識(shí)症虑,拿到消息接收方的 channel,調(diào)用 writeAndFlush() 將消息發(fā)送給消息接收方归薛。

群聊

1、A匪蝙,B主籍,C 依然會(huì)經(jīng)歷登錄流程,服務(wù)端保存用戶標(biāo)識(shí)對(duì)應(yīng)的 TCP 連接
2逛球、A 發(fā)起群聊的時(shí)候千元,將 A,B颤绕,C 的標(biāo)識(shí)發(fā)送至服務(wù)端幸海,服務(wù)端拿到之后建立一個(gè)群聊 ID,然后把這個(gè) ID 與 A奥务,B物独,C 的標(biāo)識(shí)綁定
3、群聊里面任意一方在群里聊天的時(shí)候氯葬,將群聊 ID 發(fā)送至服務(wù)端挡篓,服務(wù)端拿到群聊 ID 之后,取出對(duì)應(yīng)的用戶標(biāo)識(shí)帚称,遍歷用戶標(biāo)識(shí)對(duì)應(yīng)的 TCP 連接官研,就可以將消息發(fā)送至每一個(gè)群聊成員

總結(jié)
1、通過標(biāo)識(shí)拿到channel
2闯睹、通過 ChannelGroup戏羽,我們可以很方便地對(duì)一組 channel 進(jìn)行批量操作

群聊的成員管理

性能優(yōu)化

心跳與空閑檢測(cè)

連接假死的現(xiàn)象是:在某一端(服務(wù)端或者客戶端)看來,底層的 TCP 連接已經(jīng)斷開了楼吃,但是應(yīng)用程序并沒有捕獲到始花,因此會(huì)認(rèn)為這條連接仍然是存在的,從 TCP 層面來說所刀,只有收到四次握手?jǐn)?shù)據(jù)包或者一個(gè) RST 數(shù)據(jù)包衙荐,連接的狀態(tài)才表示已斷開。

假死導(dǎo)致兩個(gè)問題
1浮创、對(duì)于服務(wù)端忧吟,每條連接都會(huì)耗費(fèi)cpu和內(nèi)存資源,大量假死的連接會(huì)耗光服務(wù)器的資源
2斩披、對(duì)于客戶端溜族,假死會(huì)造成發(fā)送數(shù)據(jù)超時(shí)讹俊,影響用戶體驗(yàn)

通常,連接假死由以下幾個(gè)原因造成的
1煌抒、應(yīng)用程序出現(xiàn)線程堵塞仍劈,無法進(jìn)行數(shù)據(jù)的讀寫。
2寡壮、客戶端或者服務(wù)端網(wǎng)絡(luò)相關(guān)的設(shè)備出現(xiàn)故障贩疙,比如網(wǎng)卡,機(jī)房故障况既。
3这溅、公網(wǎng)丟包。公網(wǎng)環(huán)境相對(duì)內(nèi)網(wǎng)而言棒仍,非常容易出現(xiàn)丟包悲靴,網(wǎng)絡(luò)抖動(dòng)等現(xiàn)象,如果在一段時(shí)間內(nèi)用戶接入的網(wǎng)絡(luò)連續(xù)出現(xiàn)丟包現(xiàn)象莫其,那么對(duì)客戶端來說數(shù)據(jù)一直發(fā)送不出去癞尚,而服務(wù)端也是一直收不到客戶端來的數(shù)據(jù),連接就一直耗著

服務(wù)端空閑檢測(cè)

1乱陡、如果能一直收到客戶端發(fā)來的數(shù)據(jù)浇揩,那么可以說明這條連接還是活的,因此蛋褥,服務(wù)端對(duì)于連接假死的應(yīng)對(duì)策略就是空閑檢測(cè)临燃。
2、簡(jiǎn)化一下烙心,我們的服務(wù)端只需要檢測(cè)一段時(shí)間內(nèi)膜廊,是否收到過客戶端發(fā)來的數(shù)據(jù)即可,Netty 自帶的 IdleStateHandler 就可以實(shí)現(xiàn)這個(gè)功能淫茵。

IdleStateHandler 的構(gòu)造函數(shù)有四個(gè)參數(shù)爪瓜,其中第一個(gè)表示讀空閑時(shí)間,指的是在這段時(shí)間內(nèi)如果沒有數(shù)據(jù)讀到匙瘪,就表示連接假死铆铆;第二個(gè)是寫空閑時(shí)間,指的是 在這段時(shí)間如果沒有寫數(shù)據(jù)丹喻,就表示連接假死薄货;第三個(gè)參數(shù)是讀寫空閑時(shí)間,表示在這段時(shí)間內(nèi)如果沒有產(chǎn)生數(shù)據(jù)讀或者寫碍论,就表示連接假死谅猾。寫空閑和讀寫空閑為0,表示我們不關(guān)心者兩類條件;最后一個(gè)參數(shù)表示時(shí)間單位税娜。在我們的例子中坐搔,表示的是:如果 15 秒內(nèi)沒有讀到數(shù)據(jù),就表示連接假死敬矩。

在一段時(shí)間之內(nèi)沒有讀到客戶端的數(shù)據(jù)概行,是否一定能判斷連接假死呢?并不能為了防止服務(wù)端誤判弧岳,我們還需要在客戶端做點(diǎn)什么凳忙。

客戶端定時(shí)心跳

服務(wù)端在一段時(shí)間內(nèi)沒有收到客戶端的數(shù)據(jù)有兩種情況
1、連接假死
2禽炬、非假死確實(shí)沒數(shù)據(jù)發(fā)

所以我們要排除第二種情況就能保證連接自然就是假死的消略,定期發(fā)送心跳到服務(wù)端

實(shí)現(xiàn)了每隔 5 秒,向服務(wù)端發(fā)送一個(gè)心跳數(shù)據(jù)包瞎抛,這個(gè)時(shí)間段通常要比服務(wù)端的空閑檢測(cè)時(shí)間的一半要短一些,我們這里直接定義為空閑檢測(cè)時(shí)間的三分之一却紧,主要是為了排除公網(wǎng)偶發(fā)的秒級(jí)抖動(dòng)桐臊。

為了排除是否是因?yàn)榉?wù)端在非假死狀態(tài)下確實(shí)沒有發(fā)送數(shù)據(jù),服務(wù)端也要定期發(fā)送心跳給客戶端晓殊。

總結(jié)
1断凶、要處理假死問題首先我們要實(shí)現(xiàn)客戶端與服務(wù)端定期發(fā)送心跳,在這里巫俺,其實(shí)服務(wù)端只需要對(duì)客戶端的定時(shí)心跳包進(jìn)行回復(fù)认烁。
2、客戶端與服務(wù)端如果都需要檢測(cè)假死介汹,那么直接在 pipeline 的最前方插入一個(gè)自定義 IdleStateHandler却嗡,在 channelIdle() 方法里面自定義連接假死之后的邏輯。
3嘹承、通炒凹郏空閑檢測(cè)時(shí)間要比發(fā)送心跳的時(shí)間的兩倍要長一些,這也是為了排除偶發(fā)的公網(wǎng)抖動(dòng)叹卷,防止誤判撼港。

Netty 的零拷貝實(shí)現(xiàn)

Netty 的接收和發(fā)送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行 Socket 讀寫骤竹,不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝帝牡。堆內(nèi)存多了一次內(nèi)存拷貝,JVM 會(huì)將堆內(nèi)存 Buffer 拷貝一份到直接內(nèi)存中蒙揣,然后才寫入 Socket 中靶溜。ByteBuffer 由 ChannelConfig 分配,而 ChannelConfig 創(chuàng)建 ByteBufAllocator180默認(rèn)使用 Direct BufferCompositeByteBuf 類可以將多個(gè) ByteBuf 合并為一個(gè)邏輯上的 ByteBuf,避免了傳統(tǒng)通過內(nèi)存拷貝的方式將幾個(gè)小 Buffer 合并成一個(gè)大的 Buffer鸣奔。addComponents 方法將 header 與 body 合并為一個(gè)邏輯上的 ByteBuf,這 兩 個(gè) ByteBuf 在 CompositeByteBuf 內(nèi) 部 都 是 單 獨(dú) 存 在 的 ,

CompositeByteBuf 只是邏輯上是一個(gè)整體

通過 FileRegion 包裝的FileChannel.tranferTo方法 實(shí)現(xiàn)文件傳輸, 可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo) Channel墨技,避免了傳統(tǒng)通過循環(huán) write 方式導(dǎo)致的內(nèi)存拷貝問題惩阶。

通過 wrap 方法, 我們可以將 byte[] 數(shù)組、ByteBuf扣汪、ByteBuffer 等包裝成一個(gè) Netty ByteBuf 對(duì)象, 進(jìn)而避免了拷貝操作断楷。

Selector BUG:若 Selector 的輪詢結(jié)果為空,也沒有 wakeup 或新消息處理崭别,則發(fā)生空輪詢冬筒,CPU 使用率 100%,

Netty 的解決辦法:對(duì) Selector 的 select 操作周期進(jìn)行統(tǒng)計(jì)茅主,每完成一次空的select 操作進(jìn)行一次計(jì)數(shù)舞痰,若在某個(gè)周期內(nèi)連續(xù)發(fā)生 N 次空輪詢,則觸發(fā)了 epoll死循環(huán) bug诀姚。重建 Selector响牛,判斷是否是其他線程發(fā)起的重建請(qǐng)求,若不是則將原 SocketChannel 從舊的 Selector 上去除注冊(cè)赫段,重新注冊(cè)到新的 Selector181上呀打,并將原來的 Selector 關(guān)閉。

客戶端(handler 響應(yīng)處理器)

  1. 創(chuàng)建群
  2. 群消息
  3. 心跳定時(shí)器(ChannelHandlerContext -> EventExecutor -> scheduleAtFixedRate )
  4. 加入群
  5. 群成員列表
  6. 登錄
  7. 退出登錄
  8. 發(fā)消息
  9. 退出群

服務(wù)端(handler Request)

  1. 權(quán)限認(rèn)證
  2. 創(chuàng)建群
  3. 群發(fā)消息
  4. 心跳(ChannelHandlerContext -> EventExecutor -> scheduleAtFixedRate )
  5. IMHandler(壓縮 handler - 合并平行 handler)
  6. 加入群
  7. 群成員列表
  8. 登錄
  9. 退出登錄
  10. 發(fā)消息
  11. 退出群

公共

  1. 空閑檢測(cè)(IMIdleStateHandler 如果 15 秒內(nèi)沒有讀到數(shù)據(jù)糯笙,就表示連接假死贬丛,回調(diào) channelIdle,關(guān)閉channel给涕, ctx.channel().close())
  2. 編解碼

Bootstrap

Bootstrap的使用很像Builder模式豺憔,Bootstrap就是Builder,EventLoopGroup够庙、Channel和Handler等是各種Part恭应。稍有不同的是,準(zhǔn)備好各種Part后首启,并不是直接build出一個(gè)Product來暮屡,而是直接通過connect()方法使用這個(gè)Product


image.png

ChannelPipeline

ChannelPipeline實(shí)際上應(yīng)該叫做ChannelHandlerPipeline,可以把ChannelPipeline看成是一個(gè)ChandlerHandler的鏈表毅桃,當(dāng)需要對(duì)Channel進(jìn)行某種處理的時(shí)候褒纲,Pipeline負(fù)責(zé)依次調(diào)用每一個(gè)Handler進(jìn)行處理。每個(gè)Channel都有一個(gè)屬于自己的Pipeline钥飞,調(diào)用Channel#pipeline()方法可以獲得Channel的Pipeline莺掠,調(diào)用Pipeline#channel()方法可以獲得Pipeline的Channel。


image.png

事件的傳播

AbstractChannel直接調(diào)用了Pipeline的write()方法读宙,因?yàn)閣rite是個(gè)outbound事件彻秆,所以DefaultChannelPipeline直接找到tail部分的context,調(diào)用其write()方法:


image

context的write()方法沿著context鏈往前找,直至找到一個(gè)outbound類型的context為止唇兑,然后調(diào)用其invokeWrite()方法:

image

ByteBuf和設(shè)計(jì)模式

  • ByteBufAllocator - 抽象工廠模式
    在Netty的世界里酒朵,ByteBuf實(shí)例通常應(yīng)該由ByteBufAllocator來創(chuàng)建。

  • CompositeByteBuf - 組合模式
    CompositeByteBuf可以讓我們把多個(gè)ByteBuf當(dāng)成一個(gè)大Buf來處理扎附,ByteBufAllocator提供了compositeBuffer()工廠方法來創(chuàng)建CompositeByteBuf蔫耽。CompositeByteBuf的實(shí)現(xiàn)使用了組合模式

  • ByteBufInputStream - 適配器模式

ByteBufInputStream使用適配器模式,使我們可以把ByteBuf當(dāng)做Java的InputStream來使用留夜。同理匙铡,ByteBufOutputStream允許我們把ByteBuf當(dāng)做OutputStream來使用。

  • ReadOnlyByteBuf - 裝飾器模式

ReadOnlyByteBuf用適配器模式把一個(gè)ByteBuf變?yōu)橹蛔x碍粥,ReadOnlyByteBuf通過調(diào)用Unpooled.unmodifiableBuffer(ByteBuf)方法獲得:

image
  • ByteBuf - 工廠方法模式
    前面也提到過了鳖眼,我們很少需要直接通過構(gòu)造函數(shù)來創(chuàng)建ByteBuf實(shí)例,而是通過Allocator來創(chuàng)建嚼摩。從裝飾器模式可以看出另外一種獲得ByteBuf的方式是調(diào)用ByteBuf的工廠方法钦讳,比如:

ByteBuf#duplicate()
ByteBuf#slice()

PooledByteBuf內(nèi)存池原理分析

Arena -》 Chunk -》 Page

Netty的PooledArena是由多個(gè)Chunk組成的大塊內(nèi)存區(qū)域,而每個(gè)chunk則由一個(gè)或多個(gè)page組成枕面,對(duì)內(nèi)存的組織和管理也就集中在如何管理和組織Chunk和Page了


image.png

PoolChunk

chunk主要用來組織和管理多個(gè)page的內(nèi)存分配和釋放蜂厅,Chunk的Page被構(gòu)成一顆二叉樹,假設(shè)一個(gè)Chunk由16個(gè)page組成膊畴。

PoolSubpage

對(duì)于小于一個(gè)Page的內(nèi)存,Netty在page中完成分配病游,每個(gè)Page會(huì)被切分成大小相等的多個(gè)儲(chǔ)存塊唇跨,儲(chǔ)存塊的大小由第一次申請(qǐng)的內(nèi)存塊大小決定。


image.png

內(nèi)存回收策略

無論chunk還是page衬衬,都是通過狀態(tài)位來標(biāo)識(shí)內(nèi)存是否可用买猖,不同之處chunk通過在二叉樹對(duì)節(jié)點(diǎn)進(jìn)行標(biāo)識(shí)實(shí)現(xiàn),Page是通過維護(hù)塊的使用狀態(tài)來實(shí)現(xiàn)


Reactor模型

1滋尉、Reactor模型是什么?

2、多線程IO的痛點(diǎn)

服務(wù)器用一個(gè)while循環(huán)誉察,不斷監(jiān)聽端口是否有新的套接字連接摘悴,如果有,那么就調(diào)用一個(gè)處理函數(shù)處理

無法并發(fā)碾篡,效率太低虱而,如果當(dāng)前的請(qǐng)求沒有處理完,那么后面的請(qǐng)求只能被阻塞开泽,服務(wù)器的吞吐量太低

缺點(diǎn)在于資源要求太高牡拇,系統(tǒng)中創(chuàng)建線程是需要比較高的系統(tǒng)資源的,如果連接數(shù)太高,系統(tǒng)無法承受惠呼,而且导俘,線程的反復(fù)創(chuàng)建-銷毀也需要代價(jià)。

改進(jìn)方法是:

采用基于事件驅(qū)動(dòng)的設(shè)計(jì)剔蹋,當(dāng)有事件觸發(fā)時(shí)旅薄,才會(huì)調(diào)用處理器進(jìn)行數(shù)據(jù)處理。使用Reactor模式滩租,對(duì)線程的數(shù)量進(jìn)行控制赋秀,一個(gè)線程處理大量的事件。

3律想、單線程Reactor是什么猎莲?

4、單線程模式的缺點(diǎn)

5技即、多線程的Reactor是什么著洼?

6、基于線程池的改進(jìn)

7而叼、Reactor的優(yōu)點(diǎn)和缺點(diǎn)

優(yōu)點(diǎn)
1)響應(yīng)快身笤,不必為單個(gè)同步時(shí)間所阻塞,雖然Reactor本身依然是同步的葵陵;

2)編程相對(duì)簡(jiǎn)單液荸,可以最大程度的避免復(fù)雜的多線程及同步問題,并且避免了多線程/進(jìn)程的切換開銷脱篙;

3)可擴(kuò)展性娇钱,可以方便的通過增加Reactor實(shí)例個(gè)數(shù)來充分利用CPU資源;

4)可復(fù)用性绊困,reactor框架本身與具體事件處理邏輯無關(guān)文搂,具有很高的復(fù)用性;

缺點(diǎn)
1)相比傳統(tǒng)的簡(jiǎn)單模型秤朗,Reactor增加了一定的復(fù)雜性煤蹭,因而有一定的門檻,并且不易于調(diào)試取视。

2)Reactor模式需要底層的Synchronous Event Demultiplexer支持硝皂,比如Java中的Selector支持,操作系統(tǒng)的select系統(tǒng)調(diào)用支持作谭,如果要自己實(shí)現(xiàn)Synchronous Event Demultiplexer可能不會(huì)有那么高效吧彪。

3) Reactor模式在IO讀寫數(shù)據(jù)時(shí)還是在同一個(gè)線程中實(shí)現(xiàn)的,即使使用多個(gè)Reactor機(jī)制的情況下丢早,那些共享一個(gè)Reactor的Channel如果出現(xiàn)一個(gè)長時(shí)間的數(shù)據(jù)讀寫姨裸,會(huì)影響這個(gè)Reactor中其他Channel的相應(yīng)時(shí)間秧倾,比如在大文件傳輸時(shí),IO操作就會(huì)影響其他Client的相應(yīng)時(shí)間傀缩,因而對(duì)這種操作那先,使用傳統(tǒng)的Thread-Per-Connection或許是一個(gè)更好的選擇,或則此時(shí)使用改進(jìn)版的Reactor模式如Proactor模式赡艰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末售淡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慷垮,更是在濱河造成了極大的恐慌揖闸,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件料身,死亡現(xiàn)場(chǎng)離奇詭異汤纸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)芹血,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門贮泞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幔烛,你說我怎么就攤上這事啃擦。” “怎么了饿悬?”我有些...
    開封第一講書人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵令蛉,是天一觀的道長。 經(jīng)常有香客問我狡恬,道長言询,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任傲宜,我火速辦了婚禮,結(jié)果婚禮上夫啊,老公的妹妹穿的比我還像新娘函卒。我一直安慰自己,他們只是感情好撇眯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開白布报嵌。 她就那樣靜靜地躺著,像睡著了一般熊榛。 火紅的嫁衣襯著肌膚如雪锚国。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,919評(píng)論 1 290
  • 那天玄坦,我揣著相機(jī)與錄音血筑,去河邊找鬼绘沉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛豺总,可吹牛的內(nèi)容都是我干的车伞。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼喻喳,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼另玖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起表伦,我...
    開封第一講書人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤谦去,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蹦哼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳄哭,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年翔怎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窃诉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赤套,死狀恐怖飘痛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情容握,我是刑警寧澤宣脉,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站剔氏,受9級(jí)特大地震影響塑猖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谈跛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一羊苟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧感憾,春花似錦蜡励、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嫂沉,卻和暖如春稽寒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趟章。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來泰國打工杏糙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慎王,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓搔啊,卻偏偏與公主長得像柬祠,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子负芋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Netty到底是什么 從HTTP說起 有了Netty旧蛾,你可以實(shí)現(xiàn)自己的HTTP服務(wù)器莽龟,F(xiàn)TP服務(wù)器,UDP服務(wù)器锨天,...
    程序員技術(shù)圈閱讀 1,953評(píng)論 0 13
  • 好好說話毯盈,是每個(gè)父母的必修課 周末去哥哥家吃飯,恰逢嫂子生日病袄。 還在剛上班的侄女為了給嫂子一個(gè)驚喜搂赋,雖然工資不多,...
    快樂天使_快樂飛翔閱讀 126評(píng)論 0 0
  • 請(qǐng)保持一顆善良的心? 請(qǐng)成為一個(gè)善良的人 請(qǐng)承擔(dān)起自己的責(zé)任 請(qǐng)放棄不勞而獲的夢(mèng)
    遺世獨(dú)立Y閱讀 87評(píng)論 0 1
  • 這些曾活生生存在過的家人益缠,一個(gè)一個(gè)消失在歲月里脑奠,最后只剩下我一個(gè)人留在這世上。一想到這些幅慌,就會(huì)覺得眼前存在的一切宋欺,...
    沉默須臾閱讀 493評(píng)論 0 1