公司 CTO:高性能开发,你不会 Netty,怎么好意思拿 20K?
主管:这个版块用 Netty 框架就可以了呀,不会吗?
(此时,公司 CTO 路过)
某程序员:这个我真不会...
主管:好了好了,那这一块我交给别人去做,这个也不难啊?!
某程序员:大佬,我一定会好好学习,早日把这一块弄懂!
主管:行了,你先去忙吧!
CTO:慢着!这是什么情况?项目分配下去了,怎么能又安排其他同事来加班做呢?
主管:总监,项目这个版块涉及到高性能的开发,这个同事可能搞不定,眼看着项目要上线了,还是让经验丰富的工程师来做吧!
CTO(凶狠的对着程序员):那也不行,拿着这么高的工资,这都做不了,Netty 都不会用,你怎么好意思拿 20K 的?
某程序员(委屈地):大佬,我每个月只有 8K...
CTO:...哦,算了,当我没来
我不记得是谁跟我说过,“十个高性能,九个用 Netty”,我也不知道是骗我的还是咋的...
Netty 到底有这么重要吗?
我翻阅了一下我手中的各大厂面试真题,问 BIO、NIO、异步这一块的也有,Netty 出现的频次不算很高,也许我看的是初中级的面试题吧。
无论怎么说,想要涨薪升职,高性能、高并发、高可用你就不得不会。
这里,我就来分享一些 Netty 相关的核心技术点:
注:由于篇幅原因,我就简单的提取其中部分,资料已整理成文档,免费获取,需要的小伙伴可以+ VX: mxk6072
Netty 原理
Netty 是一个高性能、异步事件驱动的 NIO 框架,基于 JAVA NIO 提供的 API 实现。它提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
Netty 高性能
在 IO 编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO 多路复用技术进行处理。IO 多路复用技术通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O 多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
与 Socket 类和 ServerSocket 类相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。
1、多路复用通讯方式
Netty 架构按照 Reactor 模式设计和实现,它的服务端通信序列图如下:
客户端通信序列图如下:
Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端 Channel,由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 IO 阻塞导致的线程挂起。
异步通讯 NIO
由于 Netty 采用了异步通信模式,一个 IO 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 IO 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
零拷贝(DIRECT BUFFERS 使用堆外直接内存)
1、Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
2、Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。
3、Netty 的文件传输采用了 transferTo 方法,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。
内存池(基于内存池的缓冲区重用机制)
随着 JVM 虚拟机和 JIT 即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。
高效的 Reactor 线程模型
常用的 Reactor 线程模型有三种,Reactor 单线程模型, Reactor 多线程模型, 主从 Reactor 多线程模型。
Reactor 单线程模型
指的是所有的 IO 操作都在同一个 NIO 线程上面完成,NIO 线程的职责如下:
1) 作为 NIO 服务端,接收客户端的 TCP 连接;
2) 作为 NIO 客户端,向服务端发起 TCP 连接;
3) 读取通信对端的请求或者应答消息;
4) 向通信对端发送消息请求或者应答消息。
由于 Reactor 模式使用的是异步非阻塞 IO,所有的 IO 操作都不会导致阻塞,理论上一个线程可以独立处理所有 IO 相关的操作。从架构层面看,一个 NIO 线程确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户 Handler 可以通过 NIO 线程将消息发送给客户端。
Reactor 多线程模型
Reactor 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 IO 操作。 有专门一个 NIO 线程-Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求; 网络 IO 操作-读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送;
主从 Reactor 多线程模型
服务端用于接收客户端连接的不再是个 1 个单独的 NIO 线程,而是一个独立的 NIO 线程池。Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证等),将新创建的 SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。
等等...
Netty RPC 实现
概念
RPC,即 Remote Procedure Call(远程过程调用),调用远程计算机上的服务,就像调用本地服务一样。RPC 可以很好的解耦系统,如 WebService 就是一种基于 Http 协议的 RPC。这个 RPC 整体框架如下:
等等...
评论