一、什么是Netty?
第一:Netty 是一个 基于 NIO 模型的高性能网络通信框架,其实可以认为它是对 NIO 网络模。型的封装,提供了简单易用的 API,我们可以利用这些封装好的API 快速开发自己的网络程序。
第二:Netty 在 NIO 的基础上做了很多优化,比如零拷贝机制、高性能无锁队列、内存池等,因此性能会比 NIO 更高。
第三:Netty 可以支持多种通信协议,如 Http、WebSocket 等,并且针对数据通信的拆包黏包问题,Netty 内置了拆包策略。
二、为什么要用 Netty ?
Nety 相比于直接使用 JDK 自带的 NIO 相关的 API 来说更加易用。同时,它还具有以下特点:
1. 统一的 API,支持多种传输类型,如阻塞、非阻塞,以及 epoll、poll 等模型。
2. 我们可以使用非常少的代码来实现,多线程 Reactor 模型以及主从多线程 Reactor 模型。
3. 自带编解码器解决 TCP 粘包/拆包问题。
4. 自带各种协议栈。
5. 比直接使用 Java 库中的 NIO API 有更高的吞吐量、更低的延迟、更低的资源消耗和更少的内存复制。
6. 安全性不错,有完整的 SSL/TLS 以及 StartTLS 支持。
7. 社区活跃成熟稳定,经历了大型项目的使用和考验,而且很多开源项目都使用到了 Netty, 比如我们经常接触的 Dubbo、RocketMQ 等等。
三、Netty 可以做什么事情?
我们之所以要用 Netty,核心点还是在于解决服务器如何承载更多的用户同时访问的问题。
传统的 BIO 模型,由于阻塞的特性,使得在高并发场景中,很难获得更高的吞吐量。而后来基于 NIO 的多路复用模型虽然在阻塞方面进行了优化,但是它的 API 使用比较复杂,对于初学者来说使用不是很友好。而 Netty 是基于 NIO 的封装,提供了成熟且简单易用的 API,降低了使用成本和学习成本。
本质上来说,Netty 和 NIO 所扮演的角色是相同的,都是为了提升服务端的吞吐量,让用户获得更好的产品体验。
另外,Netty 这个中间件经过很多年的验证,在目前主流的中间件如 Zookeeper、Dubbo、RocketMQ 中都有应用。
四、Netty 的核心组件以及作用
Netty 由三层结构构成:网络通信层、事件调度器与服务编排层
在网络通信层有三个核心组件:Bootstrap、ServerBootStrap、Channel
1. Bootstrap 负责客户端启动并用来链接远程 netty server 。
2. ServerBootStrap 负责服务端监听,用来监听指定端口。
3. Channel 是负责网络通信的载体。
事件调度器有两个核心组件:EventLoopGroup 与 EventLoop
1. EventLoopGroup 本质上是一个线程池,主要负责接收 I/O 请求,并分配线程执行处理请求。
2. EventLoop 相当于线程池中的线程。
在服务编排层有三个核心组件 ChannelPipeline、ChannelHandler、ChannelHandlerContext
1. ChannelPipeline 负责将多个 Channelhandler 链接在一起。
2. ChannelHandler 针对 IO 数据的处理器,数据接收后,通过指定的 Handler 进行处理。3. ChannelHandlerContext 用来保存 ChannelHandler 的上下文信息。
五、Netty 有几种线程模型
Netty 提供了三种 Reactor 模型的支持
1. 单线程单 Reactor 模型
2. 多线程单 Reactor 模型
3. 多线程多 Reactor 模型
对于这三种线程 Reactor 模型的理解:
Reactor 模型有三个重要的组件:
1. Reactor :将 I/O 事件发派给对应的 Handler
2. Acceptor :处理客户端连接请求
3. Handlers :执行非阻塞读/写
最基本的单 Reactor 单线程模型
其中 Reactor 线程,负责多路分离套接字,有新连接到来触发 connect 事件之后,交由 Acceptor 进行处理,有 IO 读写事件之后交给 hanlder 处理。
Acceptor 主要任务就是构建 handler ,在获取到和 client 相关的 SocketChannel 之后 ,绑定到相应的 hanlder 上,对应的 SocketChannel 有读写事件之后,基于 racotor 分发,hanlder 就可以处理了(所有的 IO 事件都绑定到 selector 上,有 Reactor 分发)
多线程单 Reactor 模型
单线程 Reactor 这种实现方式有存在着缺点,从实例代码中可以看出,handler 的执行是串行的,如果其中一个 handler 处理线程阻塞将导致其他的业务处理阻塞。由于 handler 和 reactor 在同一个线程中的执行,这也将导致无法接收新的请求。
为了解决这种问题,有人提出使用多线程的方式来处理业务,也就是在业务处理的地方加入线程池异步处理,将 reactor 和 handler 在不同的线程来执行,这就是多线程单 Reactor 模型。
多线程多 Reactor 模型
在多线程单 Reactor 模型中,所有的 I/O 操作是由一个 Reactor 来完成,而 Reactor 运行在单个线程中,它需要处理包括 Accept()/read()/write/connect 操作,对于小容量的场景,影响不大。但是对于高负载、大并发或大数据量的应用场景时,容易成为瓶颈,主要原因如下:
1. 一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU 负荷达到 100%,也无法满足海量消息的读取和发送。
2. 当 NIO 线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈。
所以,我们还可以更进一步优化,引入多 Reactor 多线程模式。
1. Main Reactor 负责接收客户端的连接请求,然后把接收到的请求传递给 SubReactor(其中 subReactor 可以有多个),具体的业务 IO 处理由 SubReactor 完成。
2. Acceptor,请求接收者,在实践时其职责类似服务器,并不真正负责连接请求的建立,而只将其请求委托 Main Reactor 线程池来实现,起到一个转发的作用。
3. Main Reactor,主 Reactor 线程组,主要负责连接事件,并将 IO 读写请求转发到 SubReactor 线程池。
4. Sub Reactor,Main Reactor 通常监听客户端连接后会将通道的读写转发到 Sub Reactor 线程池中一个线程(负载均衡),负责数据的读写。在 NIO 中 通常注册通道的读(OP_READ)、写事件(OP_WRITE)。