用官方的描述:
Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它极大地简化了网络编程。
Netty是经过精心设计的,它借鉴了许多协议(如 FTP、SMTP、HTTP 以及各种基于二进制和基于文本的遗留协议)的实现经验。因此,Netty 成功地找到了一种方法,可以在不妥协的情况下实现易于开发、性能较高、并兼具了稳定性和灵活性。
如果你没有接触过netty,对上面描述还是感觉略抽象的话,那么,
用一句话总结就是:
Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用。
Netty早已被广泛应用于我们看不见的地方。
我们日常用到的一些中间件如:RocketMQ、ElasticSearch、Dubbo、gRPC、Zookeeper等,你若是详细研究,看看其源码,就会发现在其底层实现上都用了Netty。
初识Netty
Netty的优点,概括一下就是:
1)使用简单;
2)功能强大;
3)性能强悍。
Netty的特点:
1)高并发:基于 NIO(Nonblocking IO,非阻塞IO)开发,对比于 BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高;
2)传输快:传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了更高效率的传输;
3)封装好:封装了 NIO 操作的很多细节,提供了易于使用调用接口。
Netty是一个高性能、异步事件驱动的NIO框架,提供了对TCP、UDP和文件传输的支持,核心功能是让客户端和服务端两者之间进行通信交流。
如果上述这种说法太过于官方,不容易理解的话,那我换种无脑式说法。Netty的作用是,对TCP/UDP编程进行了简化和封装,提供了更容易使用的网络编程接口。
所以,说到这里,我们就明白为什么Dubbo、gRPC等RPC框架会用到Netty了吧,因为它们都存在服务消费者调用服务提供者的场景。
可以这样说,但凡用Java开发的、跟网络IO相关的中间件,基本上少不了Netty的影子。
另外,Netty的底层是依赖于JDK中的NIO,在此之上进行了优化,包括如下几点:
-
简化概念,降低编程复杂性。
-
在性能上得到很大提升。
-
解决了JDK中epoll selector空轮询导致CPU 100%的问题。
Netty & Tomcat
Netty 和 Tomcat 最大的区别在于对通信协议的支持。Tomcat 是基于 HTTP 协议的,本质是一个基于HTTP协议的Web容器,而Netty则是以TCP、UDP协议为主,并支持通过编程自定义各种协议。其它的不同点诸如:
-
底层网络通信模型不同:Tomcat 是基于阻塞的 BIO(Blocking I/O)模型实现的,而 Netty 是基于 NIO(Non-Blocking I/O)模型实现的。
-
线程模型不同:Tomcat 使用传统的多线程模型,每个请求都会分配一个线程,而 Netty 使用 EventLoop 线程模型,每个 EventLoop 负责处理多个连接,通过线程池管理 EventLoop。
-
协议支持不同:Tomcat 内置支持 HTTP 和 HTTPS 协议,而 Netty 不仅支持 HTTP 和 HTTPS 协议,还支持 TCP、UDP 和 WebSocket 等多种协议。
-
代码复杂度不同:由于Tomcat支持的功能比较全面,所以其代码相对较为复杂,而 Netty 的代码相对比较简洁、精简。
-
应用场景不同:Tomcat 适合于处理比较传统的 Web 应用程序,如传统的 MVC 模式Web应用程序;而 Netty 更适合于高性能、低延迟的网络应用程序,如游戏服务器、即时通讯服务器等。
Netty的核心组件
从某种意义上说,了解了Netty的核心组件,也就理顺的它整体的运行过程。
网上基于这五个组件的解释众说纷纭,我不得已在此基础上加入了自己的理解。
Channel:相当于socket,与另一端进行通信的通道,具备bind、connect、read、write等IO操作的能力。
EventLoop:事件循环,负责处理Channel的IO事件,一个EventLoopGroup包含多个EventLoop,一个EventLoop可被分配至多个Channel,一个Channel只能注册于一个EventLoop,一个EventLoop只能与一个Thread绑定。
ChannelFuture:channel IO事件的异步操作结果。
ChannelHandler:包含IO事件具体的业务逻辑。
ChannelPipeline:ChannelHandler的管道容器。
Netty的高性能
从宏观来讲,Netty的高性能主要在于:Reactor模式、ZeroCopy(零拷贝)和对象池。
(1)Reactor模式
通过设置不同的启动参数,Netty可以同时支持单Reactor单线程模型、单Reactor多线程模型和主从Reactor多线层模型。
上述3种的线程模型具体为:
单Reactor单线程示意图
单Reactor多线程
一个线程负责监听服务端,接受客户端TCP连接请求;一个线程同时处理多条链路,一个链路只对应一个线程
主从Reactor多线程示意图
服务器NettyReactor的工作架构
每个端口对应一个boss线程
Reactor工作原理:
①Reactor主线程 MainReactor 对象通过select 监听连接事件, 收到事件后,通过Acceptor 处理连接事件
②当 Acceptor 处理连接事件后,MainReactor 将连接分配给SubReactor
③subReactor 将连接加入到连接队列进行监听,并创建handler进行各种事件处理
④当有新事件发生时, subreactor 就会调用对应的handler处理
⑤handler 通过read 读取数据,分发给后面的worker 线程处理
⑥worker 线程池分配独立的worker 线程进行业务处理,并返回结果
⑦handler 收到响应的结果后,再通过send 将结果返回给client
⑧Reactor 主线程可以对应多个Reactor 子线程, 即MainRecator 可以关联多个SubReactor
Reactor模型思想:
分而治之 + 事件驱动(优点:模块化、高性能—把大拆小,减少阻塞时间)
分而治之
一个连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send(write)这几步。
Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。
事件驱动
相应的Task(accept、read、write)对应特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Acceptor和Handler执行。
Netty基于Reactor模型实现,Reactor模型主要由Acceptor、Reactor、Handler组成。
-
Reactor:事件分派器,将I/O事件分派给对应的Handler和Acceptor。
-
Acceptor:多路复用器,处理客户端新连接。
-
Handler:事件处理器,处理IO读写任务。
Netty 通过 Reactor 模型基于多路复用器接收并处理用户请求,内部实现了两个线程池, boss 线程池和 worker 线程池,其中 boss 线程池的线程负责处理请求的 accept 事件,当接收 到 accept 事件的请求时,把对应的 socket 封装到一个 NioSocketChannel 中,并交给 worker 线程池,其中 work 线程池负责请求的 read 和 write 事件,由对应的 Handler 处理。
Netty线程模型:
(2)ZeroCopy(零拷贝)
零拷贝技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
举例来说,如果要读取一个文件并通过网络发送它,传统方式下每个读/写周期都需要复制两次数据和切换两次上下文,而数据的复制都需要依靠CPU。通过零复制技术完成相同的操作,上下文切换减少到两次,并且不需要CPU复制数据。
Netty中的零拷贝
-
使用CompositeByteBuf 类可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝。
-
ByteBuf 支持 slice 操作,因此可以将ByteBuf分解为多个共享同一个存储区域的 ByteBuf,避免了内存的拷贝。
-
通过FileChannel.tranferTo 实现文件传输,可直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环 write方式导致的内存拷贝问题。
(3)对象池
对象池模式(The Object Pool Pattern)是单例模式的一个变种,对象池模式管理一个可代替对象的集合,组件从池中借出对象,用它来完成一些任务并当任务完成时归还该对象。
Netty中的Recycler,该类是个容器,基于ThreadLocal实现的的轻量级对象池,内部主要是一个Stack结构。当需要使用一个实例时,就弹出,当使用完毕时,就清空后入栈。
拆包粘包
TCP是一种流协议(stream protocol),先把数据流拆分成适当长度的报文段,然后TCP把数据包传给IP层,由它来通过网络将数据包传送给接收端的IP层。
MTU (Maxitum Transmission Unit),最大传输单元,是链路层对一次可以发送的最大数据的限制。
MSS(Maxitum Segment Size),最大分段大小,是TCP报文中data部分的最大长度,是传输层对一次可以发送的最大数据的限制。
MTU(1500字节) = MSS(1460字节) + TCP header (20字节) + IP header (20字节)
假设客户端分别发送两个数据包D1、D2个服务端:
-
粘包:服务端一次接收到了D1和D2两个数据包,两个包粘在一起;
-
拆包:服务端分三次读到了数据部分,第一次读到了D1包,第二次读到了D2包的部分内容,第三次读到了D2包的剩下内容;
-
拆包粘包:服务端分两次读到了数据包,第一次读到了D1和D2的部分内容,第二次读到了D2的剩下部分;
解决方案
-
设置定长消息,服务端每次读取既定长度的内容作为一条完整消息(如:不足补0000等)。
-
设置消息边界,服务端从网络流中按消息编辑分离出消息内容(如:数据边界的标识为换行符"\n")。
-
使用带消息头的协议,消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容。
Netty中的解决方案
-
FixedLengthFrameDecoder(使用定长的报文来分包)
-
DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)
-
LineBasedFrameDecoder(数据未尾添加回车换行符来分包)
-
LengthFieldBasedFrameDecoder(使用消息头和消息体来分包)
这里只列举常见的。,更长全面的内容,欢迎持续关注,后面会推出相关文章。
Netty长连接、心跳机制
在网络编程中,长连接是指客户端与服务器之间建立的连接可以保持一段时间,以便在需要时可以快速地进行数据交换。与短连接相比,长连接可以避免频繁建立和关闭连接的开销,从而提高数据传输的效率和性能。
Netty 提供了一种长连接的实现方式,即通过 Channel 的 keepalive 选项来保持连接的状态。当启用了 keepalive 选项后,客户端和服务器之间的连接将会自动保持一段时间,如果在这段时间内没有数据交换,客户端和服务器之间的连接将会被关闭。通过这种方式,可以实现长连接,避免频繁建立和关闭连接的开销。
除了 keepalive 选项之外,Netty 还提供了一种心跳机制来保持连接的状态。心跳机制可以通过定期向对方发送心跳消息,来检测连接是否正常。如果在一段时间内没有收到心跳消息,就认为连接已经断开,并进行重新连接。Netty 提供了一个 IdleStateHandler 类,可以用来实现心跳机制。IdleStateHandler 可以设置多个超时时间,当连接空闲时间超过设定的时间时,会触发一个事件,可以在事件处理方法中进行相应的处理,比如发送心跳消息。
通过使用长连接和心跳机制,可以保证客户端与服务器之间的连接处于正常的状态,从而提高数据传输的效率和性能。特别是在处理大量数据传输的场景中,长连接和心跳机制可以降低建立和关闭连接的开销,减少网络负载,提高系统的稳定性。
Netty的IO模型
Netty的IO模型是基于事件驱动的NIO(Non-blocking IO)模型。在传统的BIO(Blocking IO)模型中,每个连接都需要一个独立的线程来处理读写事件,当连接数过多时,线程数量就会爆炸式增长,导致系统性能急剧下降。而在NIO模型中,一个线程可以同时处理多个连接的读写事件,大大降低了线程的数量和切换开销,提高了系统的并发性能和吞吐量。
与传统的NIO模型相比,Netty的NIO模型有以下不同点:
-
Netty使用了Reactor模式,将IO事件分发给对应的Handler处理,使得应用程序可以更方便地处理网络事件。
-
Netty使用了多线程模型,将Handler的处理逻辑和IO线程分离,避免了IO线程被阻塞的情况。
-
Netty支持多种Channel类型,可以根据应用场景选择不同的Channel类型,如NIO、EPoll、OIO等。
Netty处理大文件的传输
搭建文件传输也是网络必须要处理的内容之一,而且,netty对这种也有比较特别的设计。
在Netty中,可以通过使用ChunkedWriteHandler处理大文件的传输。ChunkedWriteHandler是一个编码器,可以将大文件切分成多个Chunk,并将它们以ChunkedData的形式写入管道,这样就可以避免一次性将整个文件读入内存,降低内存占用。
具体使用方法如下:
-
在服务端和客户端的ChannelPipeline中添加ChunkedWriteHandler。
-
在服务端和客户端的业务逻辑处理器中,接收并处理ChunkedData。
-
在客户端向服务端发送数据时,将需要传输的文件包装成ChunkedFile并写入管道。
在传输大文件时,还需要注意以下几点:
-
使用ChunkedFile时需要指定Chunk的大小,根据实际情况选择合适的大小,一般建议不要超过8KB。
-
为了避免大文件传输过程中对网络造成影响,可以在服务端和客户端的ChannelPipeline中添加WriteBufferWaterMark,限制写入缓冲区的大小。
最后,我再总结下netty的主要特点和优势
-
异步和事件驱动:Netty采用异步非阻塞的IO模型,允许处理大量并发连接而不会阻塞应用程序线程。它使用事件驱动的方式来处理网络事件,这使得编写高效的网络应用程序变得更容易。
-
高性能:Netty在性能方面表现出色,其底层的NIO实现充分利用了现代操作系统的异步IO特性,能够处理大量并发连接和数据传输,同时保持低延迟。
-
可扩展性:Netty提供了灵活的扩展机制,可以轻松地定制和扩展功能,以满足不同应用程序的需求。它支持各种协议和编解码器,如HTTP、WebSocket、TLS/SSL等。
-
安全性:Netty内置了对TLS/SSL的支持,可以加密网络连接以确保数据的安全传输。
-
多协议支持:Netty支持多种网络协议,包括TCP、UDP、HTTP、WebSocket等,使其适用于各种应用场景。
-
大型社区和活跃开发:Netty有一个庞大的开发社区,不断更新和改进框架,以适应新的技术和需求。
以上为全部内容。