在几个月前改造dubbo时,netty4已经稳定很久了,一时手痒,按照netty3-rpc的源码克隆了一套netty4,在修正了大量的包、类型不同之后,基本保持了netty3的风格,并发量小或者数据包很小时,一切都很ok, 在进行大并发测试时,结果和netty3完全不同,基本用惨不忍睹来形容。由于当时急于开发php客户端,就把netty4-rpc当做一个失败的组件存档起来, 前几天php-dubbo开发基本完成之后,返回过来思考netty4-rpc的问题,经过仔细分析数据包的解析过程,单步跟踪源码
NettyCodecAdapter, TelnetCodec, ExchangeCoedec,发现ByteBuf的缓冲区为1024,当数据超过1024时,会调用多次Decoder.messageReceived函数,第一次分析dubbo的协议头时,是正确的,第二次之后数据就错误了,然后dubbo内部缓冲区的数据越来越长,但是仍然分析不到一个完整的dubbo数据包
因此去看netty4的源码,发现AbstractNioByteChannel中有网络数据接收的代码时这么处理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;
看见没,内核是需要ByteBuf.release的,继续通过byteBuf的一个实现PooledByteBuf分析源码,原来是实现了一个基于简单计数应用计数的循环使用的缓冲区,一旦计数变为1,该缓冲区被归还到netty4内核,被后面的数据读取线程重新使用
而我们InternalDecoder的代码为
message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
input.toByteBuffer());
直接引用了ByteBuf.toByteBuffer,继续查看源码UnpooledHeapByteBuf, 其toByteBuffer实际是对内部数据的
一个nio封装而已,因此,使用上述函数时,导致dubbo的decode保存了一个某一个ByteBuffer的内部数据,但是虽有该
buffer被归还到netty4缓冲区中被循环引用,下一次可能被其他读写线程重新改写数据,因此,高并发下当缓冲区被重复使用时,bytebuf将由于计数问题不断被使用,而解码器中缺傻傻等待。
解决方案
1.通过byteBuf的retain和release函数保证计数的有效性,通过程序例外或者缓冲区被使用完成时候归还ByteBuf到netty4内核
2.拷贝数据到dubbo的缓冲区中
思考:
netty3 是否也有该问题呢???