最近我學(xué)習(xí)了NIO相關(guān)的知識(shí),然后發(fā)現(xiàn)了Netty這個(gè)基于NIO的網(wǎng)絡(luò)應(yīng)用框架,于是就研究起Netty框架源碼,來(lái)好好體會(huì)一下網(wǎng)絡(luò)框架的設(shè)計(jì)理念和思想.
?這個(gè)系列的文章不僅會(huì)總結(jié)Netty各個(gè)模塊的源碼原理,也會(huì)寫出一些自己對(duì)這些設(shè)計(jì)的理解和體會(huì).
?我基本按照并發(fā)編程網(wǎng)上這個(gè)系列文章的順序來(lái)進(jìn)行系列文章的順序,不同的是我是基于Netty4.1的源碼進(jìn)行分析和講解.
?為了節(jié)約你的時(shí)間,本篇文章主要內(nèi)容如下:
- Netty的Buffer的內(nèi)存模型,涉及讀寫指針
- Netty的Buffer框架
- Netty的Pool原理,輕量對(duì)象池
Buffer
Java NIO中的Buffer用于和NIO通道進(jìn)行交互,數(shù)據(jù)可以從通道讀入緩沖區(qū),也可以從緩沖區(qū)寫入到通道中.所以說(shuō),Buffer其實(shí)就是一塊可以讀寫數(shù)據(jù)的內(nèi)存,我們將其包裝為一個(gè)Java對(duì)象來(lái)提供一系列讀寫操作.
?Netty并沒(méi)有直接使用Java NIO的Buffer實(shí)現(xiàn),而是自己實(shí)現(xiàn)了一套Buffer框架來(lái)滿足自己的業(yè)務(wù)或者性能需求.
ByteBuf的基本原理
讀寫指針的作用
不同于NIO Buffer的讀寫指針共用原理,ByteBuf擁有readerIndex
,writerIndex
兩個(gè)指針.下面我們就來(lái)詳細(xì)的講解一下ByteBuf的內(nèi)部原理.
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
從示意圖中我們可以看出readerIndex
和writerIndex
最多可以將整個(gè)內(nèi)容空間劃分為三塊:廢棄區(qū)
,可讀區(qū)
和可寫區(qū)
.下面我們就來(lái)看一下不同操作下的兩個(gè)指針的變化.
- 在初始化狀態(tài)下,假設(shè)capacity為20,
readerIndex
和writerIndex
都為0,整個(gè)空間中只存在可寫區(qū).此時(shí)只能寫,不能讀,進(jìn)行讀操作會(huì)拋出異常.
+---------------------------------------------------------+
| writable bytes (got more space) |
+---------------------------------------------------------+
| |
readerIndex(0)
writerIndex(0) <= capacity
- 寫入10個(gè)字節(jié)的數(shù)據(jù),
writerIndex
指向10,readerIndex
不會(huì)改變,所有內(nèi)容空間中有可讀區(qū)和可寫區(qū).大小都是10字節(jié).
+-------------------+------------------+------------------+
| readable bytes | writable bytes |
| (CONTENT) | |
+--------- --------+------------------+------------------
| | |
readerIndex(0) <= writerIndex(10) <= capacity
- 讀取5個(gè)字節(jié)的內(nèi)容,
writerIndex
不變,readerIndex
加5,指向了5.此時(shí)內(nèi)容空間分為了5字節(jié)的廢棄區(qū),5字節(jié)的可讀區(qū)和10字節(jié)的可寫區(qū).
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex(5) <= writerIndex(10) <= capacity
- 調(diào)用
discardReadBytes
方法后,將廢棄區(qū)的內(nèi)容舍棄掉,readerIndex
又指向了0,writerIndex
指向了5,相當(dāng)于可讀區(qū)和可寫區(qū)整體向左平移了5個(gè)字節(jié).
+------------------+--------------------------------------+
| readable bytes | writable bytes (got more space) |
+------------------+--------------------------------------+
| | |
readerIndex (0) <= writerIndex (5) <= capacity
零拷貝
OS層次上Zero-copy
,就是在操作數(shù)據(jù)時(shí),不需要將數(shù)據(jù)buffer從一個(gè)內(nèi)存區(qū)域拷貝到另一個(gè)內(nèi)存區(qū)域,因?yàn)闇p少了一次內(nèi)存的拷貝,因此CPU的效率得到了提升.
?Netty
的zero-copy
體現(xiàn)在很多方面.比如Buffer的compose,duplicate,slice操作時(shí)不會(huì)拷貝底層的數(shù)據(jù).而是通過(guò)ByteBuf對(duì)象的組合來(lái)實(shí)現(xiàn)上述的操作
-
Netty提供了
CompositeByteBuf
類,可以將多個(gè)ByteBuf
組合成一個(gè)邏輯上的Buffer,避免了各個(gè)buffer之間的拷貝,CompositeByteBuf
并不擁有底層的數(shù)據(jù),而是通過(guò)擁有兩個(gè)buffer對(duì)象,從這兩個(gè)buffer對(duì)象中獲取數(shù)據(jù)來(lái)對(duì)外提供看似合并了的數(shù)據(jù).比如我們將一份協(xié)議數(shù)據(jù)的頭部buffer和消息體buffer合并成一個(gè)Buffer.
1495838149-5833cca6c5c3d_articlex.png
?如上圖所示,所有底層的數(shù)據(jù)還是存儲(chǔ)在header和body這兩個(gè)真實(shí)的buffer中. 對(duì)于
ByteBuf
的slice
和duplicate
操作也是如此,不同的buffer共享了相同的底層數(shù)據(jù),而不是進(jìn)行底層數(shù)據(jù)的拷貝.具體使用到的Buffer類型為DuplicatedByteBuf
和SlicedByteBuf
.誰(shuí)說(shuō)是共享的底層數(shù)據(jù),但是通過(guò)對(duì)writerIndex
和readerIndex
兩個(gè)指針的操作來(lái)實(shí)現(xiàn)slice和duplicate的功能.Netty使用
wrap
操作將byte數(shù)組轉(zhuǎn)化為ByteBuf
對(duì)象時(shí),將byte數(shù)組包裹到對(duì)象中,而不是拷貝數(shù)組存放到對(duì)象中.Netty 中使用 FileRegion 實(shí)現(xiàn)文件傳輸?shù)牧憧截? 不過(guò)在底層 FileRegion 是依賴于 Java NIO FileChannel.transfer 的零拷貝功能.
Pool和Reference Count
4.0之后的版本實(shí)現(xiàn)了高性能的Buffer池,分配策略則是結(jié)合了buddy allocation和slab allocation的jemalloc變種祠饺,實(shí)現(xiàn)類為PoolArena
,這樣的話,可以在頻繁分配和釋放Buffer時(shí)緩解GC壓力,還可以在初始化新buffer時(shí)減少內(nèi)存帶寬消耗(初始化時(shí)不可避免的要給buffer數(shù)組賦初始值).
?ByteBuf
引入了Reference Count
機(jī)制,你需要在不適用它的時(shí)候調(diào)用ReferenceCountUtil.release
方法來(lái)減少它的引用.
后記
?感覺(jué)自己在研究或在閱讀源代碼時(shí)還是有些問(wèn)題,起始ByteBuf
并不是Netty
的關(guān)鍵所在,不應(yīng)該花費(fèi)這么長(zhǎng)時(shí)間.以后還是要帶著目的來(lái)看源碼,不能把時(shí)間浪費(fèi)在一些代碼細(xì)節(jié)上.