在幾個月前改造dubbo時,netty4已經(jīng)穩(wěn)定很久了,一時手癢,按照netty3-rpc的源碼克隆了一套netty4,在修正了大量的包、類型不同之后,基本保持了netty3的風(fēng)格,并發(fā)量小或者數(shù)據(jù)包很小時,一切都很ok, 在進(jìn)行大并發(fā)測試時,結(jié)果和netty3完全不同,基本用慘不忍睹來形容。由于當(dāng)時急于開發(fā)php客戶端,就把netty4-rpc當(dāng)做一個失敗的組件存檔起來, 前幾天php-dubbo開發(fā)基本完成之后,返回過來思考netty4-rpc的問題,經(jīng)過仔細(xì)分析數(shù)據(jù)包的解析過程,單步跟蹤源碼
NettyCodecAdapter, TelnetCodec, ExchangeCoedec,發(fā)現(xiàn)ByteBuf的緩沖區(qū)為1024,當(dāng)數(shù)據(jù)超過1024時,會調(diào)用多次Decoder.messageReceived函數(shù),第一次分析dubbo的協(xié)議頭時,是正確的,第二次之后數(shù)據(jù)就錯誤了,然后dubbo內(nèi)部緩沖區(qū)的數(shù)據(jù)越來越長,但是仍然分析不到一個完整的dubbo數(shù)據(jù)包
因此去看netty4的源碼,發(fā)現(xiàn)AbstractNioByteChannel中有網(wǎng)絡(luò)數(shù)據(jù)接收的代碼時這么處理ByteBuf的
ByteBuf byteBuf = null;
int messages = 0;
boolean close = false;
try {
int totalReadAmount = 0;
boolean readPendingReset = false;
do {
byteBuf = allocHandle.allocate(allocator);
int writable = byteBuf.writableBytes();
int localReadAmount = doReadBytes(byteBuf);
if (localReadAmount <= 0) {
// not was read release the buffer
byteBuf.release();
close = localReadAmount < 0;
break;
}
if (!readPendingReset) {
readPendingReset = true;
setReadPending(false);
}
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
看見沒,內(nèi)核是需要ByteBuf.release的,繼續(xù)通過byteBuf的一個實(shí)現(xiàn)PooledByteBuf分析源碼,原來是實(shí)現(xiàn)了一個基于簡單計(jì)數(shù)應(yīng)用計(jì)數(shù)的循環(huán)使用的緩沖區(qū),一旦計(jì)數(shù)變?yōu)?,該緩沖區(qū)被歸還到netty4內(nèi)核,被后面的數(shù)據(jù)讀取線程重新使用
而我們InternalDecoder的代碼為
message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
input.toByteBuffer());
直接引用了ByteBuf.toByteBuffer,繼續(xù)查看源碼UnpooledHeapByteBuf, 其toByteBuffer實(shí)際是對內(nèi)部數(shù)據(jù)的
一個nio封裝而已,因此,使用上述函數(shù)時,導(dǎo)致dubbo的decode保存了一個某一個ByteBuffer的內(nèi)部數(shù)據(jù),但是雖有該
buffer被歸還到netty4緩沖區(qū)中被循環(huán)引用,下一次可能被其他讀寫線程重新改寫數(shù)據(jù),因此,高并發(fā)下當(dāng)緩沖區(qū)被重復(fù)使用時,bytebuf將由于計(jì)數(shù)問題不斷被使用,而解碼器中缺傻傻等待。
解決方案
1.通過byteBuf的retain和release函數(shù)保證計(jì)數(shù)的有效性,通過程序例外或者緩沖區(qū)被使用完成時候歸還ByteBuf到netty4內(nèi)核
2.拷貝數(shù)據(jù)到dubbo的緩沖區(qū)中
思考:
netty3 是否也有該問題呢???
只有nioBuffer方法,而該方法是將array數(shù)組轉(zhuǎn)換為ByteBuffer對象。
照理說應(yīng)該沒有問題的,因?yàn)閍rray數(shù)組在使用中并不可變的。
如果netty在使用是PooledUnsafeDirectByteBuf,那么就是有問題的,使用nioBuffer的時候,實(shí)際上還持有對原有ByteBuffer的引用。