感謝支付寶同事[易鴻偉]在本站發佈此文。html
上文netty-mina深刻學習與對比(一)講了對netty-mina的線程模型以及任務調度粒度的理解,這篇則主要是講nio編程中的注意事項,netty-mina的對這些注意事項的實現方式的差別,以及業務層會如何處理這些注意事項。java
java nio若是是non-blocking的話,在每次write(bytes[N])的時候,並不會將N字節所有write出去,每次write僅一部分(具體大小和tcp_write_buffer有關)。那麼,mina和netty是怎麼處理這種狀況的呢?編程
mina一、2,netty3的方式基本一致。 在發送端每一個session均有一個writeBufferQueue,有這樣一個隊列,能夠保證 寫入與寫出均有序。在真正write時,大體邏輯均是一一將隊列中的writeBuffer取出,寫入socket。但有一些不一樣的是,mina1是每次 peek一次,當該buffer所有寫出以後再poll(mina3也是這種機制);而mina二、netty3則是直接poll第一個,將其存爲 currentWriteRequest,直到currentWriteRequest所有寫出以後,纔會再poll下一個。這樣的作法是爲了省幾回 peek的時間麼?api
同時mina、netty在write時,有一種spin write的機制,即循環write屢次。mina1的spin write count爲256,寫死在代碼裏了,表示256有點大;mina2這個機制廢除但代碼保留;netty3則能夠配置,默認爲16。netty在這裏略勝 一籌!session
netty4與netty3的機制差很少,可是netty4爲這個事情特地寫了一個ChannelOutboundBuffer類,輸出隊列寫在了 該類的flushed:Object[]成員中,但表示ChannelOutboundBuffer這個類的代碼有點長,就暫不深究了。T_T併發
如第三段內容,每次write只是輸出了一部分數據,read同理,也有可能只會讀入部分數據,這樣就是致使讀入的數據是殘缺的。而mina和netty默認不會理會這種因爲nio致使的數據分片,須要由業務層本身額外作配置或者處理。框架
nfs-rpc在協議反序列化的過程當中,就會考慮這個的問題,依次讀入每一個字節,當發現當前字節或者剩餘字節數不夠時,會將buf的readerIndex設置爲初始狀態。具體的實現,有興趣的同窗能夠學習nfs-rpc:ProtocolUtils.decodesocket
nfs-rpc在decode時,出現錯誤就會將buf的readerIndex設爲0,把readerIndex設置爲0就必需要有個前提假設:每次decode時buf是同一個,即該buf是複用的。那麼,具體狀況是怎樣呢?tcp
我看讀mina與netty這塊的代碼,發現主要演進與不一樣的點在兩個地方:讀buffer的建立與數據分片的處理方式。ide
mina:
mina一、2的讀buffer建立方式比較土,在每次read以前,會從新allocate一個新的buf對象,該buf對象的大小是根據讀入數 據大小動態調整。當本次讀入數據等於該buf大小,下一次allocate的buf對象大小會翻倍;當本次讀入數據不足該buf大小的二分之一,下一次 allocate的buf對象一樣會縮小至一半。須要注意的是,*2與/2的代碼均可以用位運算,可是mina1竟沒用位運算,有意思。
mina一、2處理數據分片能夠繼承CumulativeProtocolDecoder,該decoder會在session中存入 (BUFFER, cumulativeBuffer)。decode過程爲:1)先將message追加至cumulativeBuffer;2)調用具體的decode 邏輯;3)判斷cumulativeBuffer.hasRemaining(),爲true則壓縮cumulativeBuffer,爲false則直 接刪除(BUFFER, cumulativeBuffer)。實現業務的decode邏輯能夠參考nfs-rpc中MinaProtocolDecoder的代碼。
mina3在處理讀buffer的建立與數據分片比較巧妙,它全部的讀buffer共用一個buffer對象(默認64kb),每次均會將讀入的數 據追加至該buffer中,這樣即省去了buffer的建立與銷燬事件,也省去了cumulativeDecoder的處理邏輯,讓代碼很清爽啊!
netty:
netty3在讀buffer建立部分的代碼仍是挺有意思的,首先,它建立了一個SocketReceiveBufferAllocator的 allocate對象,名字爲recvBufferPool,可是裏面代碼徹底和pool扯不上關係;其次,它每次建立buffer也會動態修改初始大小 的機制,它設計了232個大小檔位,最大值爲Integer.MAX_VALUE,沒有具體考究,這種實現方式彷佛比每次大小翻倍優雅一點,具體代碼能夠 參考:AdaptiveReceiveBufferSizePredictor。
對應mina的CumulativeProtocolDecoder類,在netty中則是FrameDecoder和 ReplayingDecoder,沒深刻只是大體掃了下代碼,原理基本一致。BTW,ReplayingDecoder彷佛挺強大的,有興趣的能夠看看 這兩篇:
[High speed custom codecs with ReplayingDecoder]
[An enhanced version of ReplayingDecoder for Netty]
netty4在讀buffer建立部分機制與netty3大同小異,不過因爲netty有了ByteBufAllocator的概念,要想每次不從新建立銷燬buffer的話,能夠採用PooledByteBufAllocator。
在處理分片上,netty4抽象出了Message這樣的概念,個人理解就是,一個Message就是業務可讀的數據,轉換Message的抽象 類:ByteToMessageDecoder,固然也有netty3中的ReplayingDecoder,繼承自 ByteToMessageDecoder,具體能夠研究代碼。
mina:
須要說明的是,只有mina一、2纔有本身的buffer類,mina3內部只用nio的原生ByteBuffer類(提供了一個組合buffer的代理類-IoBuffer)。mina一、2自建buffer的緣由以下:
第一條比較好理解,即提供了更爲方便的方法用以操做buffer。第二條則是以爲nio的ByteBuffer是定長的,沒法自動擴容或者縮容,所 以提供了自動擴/縮容的方法:IoBuffer.setAutoExpand, IoBuffer.setAutoShrink。可是擴/縮容的實現,也是基於nio的ByteBuffer,從新 ByteBuffer.allocate(capacity),再把原有的數據拷貝過去。
netty:
在我前面的博文([Netty 4.x學習筆記 – ByteBuf])我已經提到這些緣由:
以上理由來自netty3的API文檔:[Package org.jboss.netty.buffer]),netty4沒見到官方的說法,可是我以爲還得加上一個更爲重要也是最爲重要的理由,就是能夠實現buffer池化管理。
mina:
mina的實現較爲基礎,僅僅只是在ByteBuffer上的一些簡單封裝。
netty:
netty3與netty4的實現大體相同(ChannlBuffer -> ByteBuf),具體能夠參見:[Netty 4.x學習筆記 – ByteBuf](http://hongweiyi.com/2014/01/netty-4-x-bytebuf/),netty4實現了 PooledByteBufAllocator,傳聞是能夠大大減小GC的壓力,可是官方不保證沒有內存泄露,我本身壓測中也出現了內存泄露的警告,建議 生產中謹慎使用該功能。
netty5.x有一個更爲高級的buffer泄露跟蹤機制,PooledByteBufAllocator也已經默認開啓,有機會能夠嘗試使用一下。
原創文章,轉載請註明: 轉載自併發編程網 – ifeve.com本文連接地址: Netty-Mina深刻學習與對比(二)