dubbo中的那些“坑"(3)-netty4-rpc网络接口中的高并发的bug

原创
2014/12/02 23:13
阅读数 9.8K

在几个月前改造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 是否也有该问题呢???










展开阅读全文
打赏
0
12 收藏
分享
加载中
即便是ByteBuf的循环使用,有volatile计数也不会有问题吧,还有这个“当数据超过1024时,会调用多次Decoder.messageReceived函数”确定是这样?debug?
2017/04/20 08:34
回复
举报
查看了一下代码,版本是netty-all.4.0.18
跟楼主说的有点出入:
UnpooledHeapByteBuf方法是没有toByteBuffer方法的。
只有nioBuffer方法,而该方法是将array数组转换为ByteBuffer对象。
照理说应该没有问题的,因为array数组在使用中并不可变的。

如果是PooledUnsafeDirectByteBuf实现,那么就是有问题的,调用nioBuffer方法的时候,实际上还持有对原有ByteBuffer的引用。
2016/06/07 14:52
回复
举报
还有, php客户端构建一个数组,调用后端java的服务,有没有出现乱码的问题呢,怎么解决的呢
2016/04/14 14:53
回复
举报
php+dubbo中, 我们是将后端java提供http服务,php客户端通过url即 http://nginx-host/order直连到服务端, nginx-host是前置负载均衡机, 通过它代理到后端具体某台服务。 请问你们的做法是什么呢?
2016/04/14 14:51
回复
举报
netty4相对于netty3线程模型改了 不能简单的改包名吧
2016/03/02 13:39
回复
举报
能否给出具体的代码呢?不了解netty谢谢。
2015/07/02 11:30
回复
举报
阿阮博主

引用来自“daishuli”的评论

请问,php做客户端时,是通过url直连的么?还需要消费者么?
定制的客户端访问接口
2015/06/29 20:55
回复
举报
请问,php做客户端时,是通过url直连的么?还需要消费者么?
2015/06/29 19:31
回复
举报
阿阮博主
有点大,包名变了,一些类名也改了
2015/03/14 18:30
回复
举报
兄弟 升级  dubbo中 netty到4.x版本,用于生产环境了么,改动大不?
2015/03/14 16:03
回复
举报
更多评论
打赏
10 评论
12 收藏
0
分享
返回顶部
顶部