Netty是什么,为什么要用Netty
Netty是一款基于NIO开发的网络通信框架,在易用的同时,没有丧失可维护性和性能等优势。
特点:高并发(NIO)、传输快(零拷贝)、封装好。
零拷贝是什么
零拷贝主要体现在三个方面:
- Netty的接受和发送ByteBuffer采用Direct Buffers,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。
- 提供了组合Buffer对象,可以聚合多个ByteBuffer对象,避免了传统通过内存拷贝将几个小buffer合并成一个大Buffer。
- Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
Netty有什么组件
Bootstrap: 引导类,提供一个用于应用程序网络层配置的容器。
Channel:底层网络传输API必须提供给I/O操作的接口。
ChannelHandler:channelHandler支持很多协议,并且提供用于数据处理的容器。
ChannelInboundHandler:这个类型接收到入站事件(包括接收到的数据)可以处理应用程序逻辑
ChannelPipeline:提供一个容器给channelHandler链并提供一个API用于管理沿着链入站和出站事件的流动。每个Channel都有自己的ChannelPipeline,是当Channel创建时自动被创建的。
EventLoop:用于处理I/O操作。一个单一的EventLoop通常会处理多个Channel事件。
EventLoopGroup:可以含有多于一个的EventLoop和提供一个迭代用于检索清单中的下一个。
ChannelFuture:Netty所有的I/O操作都是异步的。因为一个操作可能无法立即返回,我们需要有一种方法在以后确定这个操作的返回结果。出于这个目的,Netty提供了接口ChannelFuture,它的addLiListener方法注册了一个ChannelFutureListener,当操作完成时,可以被通知。
关系:
- Socket和Channel是一对一。
- Channel和EventLoop是多对一。
- EventLoop和EventLoopgroup是多对一。
- EventLoop和Thread是一对一。
- ChannelHandler和ChannelPipeline是多对一。
- ChannelPipeline和Channel是一对一。
粘包和半包问题
粘包:发送方每次写入数据<套接字缓冲区的大小;接收方读取数据不及时。
半包:发送方每次写入数据>套接字缓冲区的大小;发送的数据大于协议的最大传输单元,必须拆包。
根本原因:TCP 是流式协议,消息无边界。
解决方法:
- 改成短连接(不推荐)
- 封装成帧(固定长度、分隔符、专门的length字段) | 方式 | 解码 | 编码 | | —- | —- | —- | | 固定长度 | FixedLengthFrameDecoder | / | | 分隔符 | DelimiterBasedFrameDecoder | / | | 专门的length字段 | LengthFieldBasedFrameDecoder | LengthFieldPrepender |
Netty的序列化问题
- XML(较差)
- JSON
- Fastjson
- Thrift
- Avro(优秀)
- Protobuf(优秀)
Netty线程模型
Netty结合了Selector和Reactor模式设计了高效的线程模型,主要角色包括Selector、EventLoopGroup/EventLoop、ChannelPipeline。
首先,Selector是一个多路复用器,负责将就绪的IO事件分离出来,BossEventLoopGroup会负责接收,通常是一个单线程,根据事件分发到WorkerEventLoopGroup,随后通过ChannelPipeline将请求进行处理,ChannelPipeline内部是一个ChannelHandler链表,负责具体的处理。
优雅退出
- 把NIO线程的状态位设置成ST_SHUTTING_DOWN状态,不再处理新的消息(不允许再对外发送消息);
- 退出前的预处理操作:把发送队列中尚未发送或者正在发送的消息发送完、把已经到期或者在退出超时之前到期的定时任务执行完成、把用户注册到NIO线程的退出Hook任务执行完成;
- 资源的释放操作:所有Channel的释放、多路复用器的注册和关闭、所有队列和定时任务的清空取消,最后是NIO线程的退出。
内存分配
主要分为Arena、ChunkList、Chunk、Page、Subpage这5个层级。
- 需要分配的内存小于PageSize时,分配tiny或者 small内存。
- 需要分配的内存介于PageSize和 ChunkSize时,则分配normal内存。
- 需要分配的内存大于ChunkSize时,则分配huge内存(非池化内存)。