netty解码器分析和记录
- netty半包处理器分析
- netty验证demo如下
- 验证粘包和拆包
- 验证客户端一个字节一个字节写
- netty解码总结
- netty Channel累积缓冲区cumulation
- 为什么netty不适合传输文件
- 为什么dubbo不适合传输问题件
- github branch-netty分支。
netty解码总结
先确定获取都一个完整包(采用netty现有的或者继承ByteToMessageDecoder重写),然后触发pipeline进行解码,解码动作我们自己实现(只继承inbound适配器即可,不需要继承ByteToMessageDecoder )
netty Channel累积缓冲区cumulation
每个Channle都有个与之对应的Pipeline,如果pipeline有继承了ByteToMessageDecoder的解码器,那么此Channle就有个累积缓冲区cumulation,累积缓冲区是什么时候清空的呢? 最大允许多大呢?
累积缓冲区cumulation在不可读的情况下,会被清空,不可读,说明报文是(多个)完整的且已经被解码,即报文都被解码后,累积缓冲区被清空。
累加缓冲区cumulation最大可以多大呢?从ByteToMessageDecoder内看不出来。
为什么netty不适合传输文件
netty处理read事件: 由IO线程自旋,处理注册到Selector上的通道的事件。
NioEventLoop#run -> NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel) -> io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read@Override public final void read() { //省略 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);//把socket数据写入到缓冲区 //省略 pipeline.fireChannelRead(byteBuf);//触发pipeline的channelRead byteBuf = null; //省略 } while (++ messages < maxMessagesPerRead);//最大连续读取16次 pipeline.fireChannelReadComplete();//触发pipeline的channelReadComplete //省略 } catch (Throwable t) { handleReadException(pipeline, byteBuf, t, close); } finally { //省略 } }原因有如下两处:
1.由于EventLoop是串行执行,如果文件过大,那么在读取输入流的时候,在这里循环16次才结束,如果大多数channel都这样,严重会导致其他channel的read事件处理阻塞。
2.每次读取后,触发pipeline的channelRead,把读取到的数据保存到累加缓冲区io.netty.handler.codec.ByteToMessageDecoder#cumulation,这样会导致累加缓冲区使用内存过大。
为什么dubbo不适合传输问题件
dubbo默认报文最大是8m,为什么这样设置,官方文档为什么不建议传输文件或者大报文呢?
原因如下:
1.dubbo采用的序列化,需要一次性把对象序列化,如果对象很大(包含文件),那么一次性序列化对象并加载到内存,会导致oom。
2.dubbo默认使用的netty,且是单连接模型,一个客户端对一个服务端只有一个tcp连接,netty消息发送也是采用的串行无锁化,如果一个对象比较大,会导致这个channel一直在发送(write&flush),从而导致其他channel的发送排队阻塞(在taskQueue内排队无法及时处理),迟迟无法发送出去,最终会导致客户端其它的consumer等待超时。
3.大对象也会导致dubbo服务端读取,从而导致阻塞其它channel的数据读取。 和2的原因一样,netty使用的串行无锁化,如果一个任务执行过程,就会到其它任务阻塞。
既然单连接模型有这个缺点,为什么dubbo还要采用呢?因为省资源,tcp资源是宝贵的,且单连接可以满足rpc的场景;rpc的场景通常是服务端少,客户端很多,如果为每个客户端都创建tcp连接,会导致tcp连接数爆满,因为不知道具体有多少个客户端会调用服务端。
dubbo也是可以使用多连接模型的,在客户端发送时,采用轮询机制从tcp连接池内选择一个tcp连接进行发送。但是通常不建议这样。
因此:dubbo仅适合业务小报文传输(默认小于8m),不适合大报文和文件传输。
为什么http适合传输文件
http适合传输文件需要从客户端和服务端来说
客户端
使用httpclient等工具传输文件,比如要传输的文件在本地,每次只读取文件的一个 Buffer 大小,然后将这个 Buffer 的数据使用 Socket 发送即可;在这种方式下,同时存在于内存中的数据,只会有一个 Buffer 大小,不会有 Dubbo 那样将全部数据读取至内存的问题。
服务端
针对web服务容器是tomcat来说,Tomcat 在读取文件报文(form-data)时,会先将报文暂存至磁盘,然后通过 FileItem 读取磁盘中的报文内容。所以在对于 Server 端来说,不会一次性将完整的报文数据读取至内存中,也就不会有内存占用过高的问题。
因此平时的上传文件,都是使用的客户端直连oss上传,不经过中间系统,这样也不会导致我们系统的连接阻塞、带宽占用等问题。