1、为什么要用 Netty
相比于直接使用 JDK 自带的 NIO 相关的 API 来说更加易用。统一的 API,支持多种传输类型,阻塞和非阻塞的。 简单而强大的线程模型。自带编解码器解决 TCP 粘包/拆包问题。自带各种协议栈。真正的无连接数据包套接字支持。比直接使用 Java 核心 API 有更高的吞吐量、更低的延迟、更低的资源消耗和更少的内存复制。安全性不错,有完整的 SSL/TLS 以及 StartTLS 支持。社区活跃成熟稳定,经历了大型项目的使用和考验,而且很多开源项目都使用到了 Netty, 比如我们经常接触的 Dubbo、RocketMQ 等等。
理论上来说,NIO 可以做的事情 ,使用 Netty 都可以做并且更好。Netty 主要用来做网络通信 : 作为 RPC 框架的网络通信工具 :我们在分布式系统中,不同服务节点之间经常需要相互调用,这个时候就需要 RPC 框架了。不同服务节点之间的通信是如何做的呢?可以使用Netty 来做。比如我调用另一个节点的方法的话,至少是要让对方知道我调用的是哪个类中的哪个方法以及相关参数吧! 实现一个自己的 HTTP 服务器 :通过 Netty 我们可以自己实现一个简单的 HTTP 服务器,这个大家应该不陌生。说到 HTTP 服务器的话,作为 Java 后端开发,我们一般使用Tomcat 比较多。一个最基本的 HTTP 服务器可要以处理常见的 HTTP Method 的请求,比如 POST 请求、GET 请求等等。
2、Netty 核心组件有哪些?分别有什么作用?
Channel
Channel 接口是 Netty 对网络操作抽象类,它除了包括基本的 I/O 操作,如 bind()、 connect()、read()、write() 等。比较常用的 Channel 接口实现类是 NioServerSocketChannel(服务端)和NioSocketChannel(客户端),这两个 Channel 可以和 BIO 编程模型中的 ServerSocket 以及 Socket 两个概念对应上。Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。
EventLoop
EventLoop 的主要作用实际就是负责监听网络事件并调用事件处理器进行相关 I/O 操作 的处理。
ChannelFuture
Netty 是异步非阻塞的,所有的 I/O 操作都为异步的。因此,我们不能立刻得到操作是否 执行成功,但是,你可以通过 ChannelFuture 接口的 addListener() 方法注册一ChannelFutureListener,当操作执行成功或者失败时,监听就会自动触发返回结果。
ChannelHandler 和 ChannelPipeline
ChannelHandler 是消息的具体处理器。他负责处理读写操作、客户端连接等事情.ChannelPipeline 为 ChannelHandler 的链,提供了一个容器并定义了用于沿着链传播入站和出站事件流的 API 。当 Channel 被创建时,它会被自动地分配到它专属的
ChannelPipeline。
我们可以在 ChannelPipeline 上通过 addLast() 方法添加一个或者多个 ChannelHandler ,因为一个数据或者事件可能会被多个 Handler 处理。当一 个 ChannelHandler 处理完之后就将数据交给下一个ChannelHandler 。
3、Netty 服务端和客户端的启动过程是怎样的?
服务端
1. 首先你创建了两个 NioEventLoopGroup 对象实例:bossGroup 和 workerGroup。bossGroup : 用于处理客户端的 TCP 连接请求。 workerGroup :负责每一条连接的具体读写数据的处理逻辑,真正负责 I/O 读写操作,交由对应的 Handler 处理。
2. 接下来 我们创建了一个服务端启动引导/辅助类:ServerBootstrap,这个类将引导我们进行服务端的启动工作。通过 .group() 方法给引导类 ServerBootstrap 配置两大线程组,确定了线程模型。
3. 通过 channel()方法给引导类 ServerBootstrap 指定了 IO 模型为 NIO ,NioServerSocketChannel 指定服务端的 IO 模型为 NIO,与 BIO 编程模型中的ServerSocket 对应。 NioSocketChannel : 指定客户端的 IO 模型为 NIO, 与 BIO 编程模型中的 Socket 对应.通过 .childHandler()给引导类创建一个 ChannelInitializer ,然后制定了服务端消 息的业务处理逻辑 HelloServerHandler 对象
4.调用 ServerBootstrap 类的 bind()方法绑定端口
客户端
创建一个 NioEventLoopGroup 对象实例创建客户端启动的引导类是 Bootstrap通过 .group() 方法给引导类 Bootstrap 配置一个线程组 通过 channel()方法给引导类 Bootstrap 指定了 IO 模型为 NIO 通过 .childHandler()给引导类创建一个 ChannelInitializer ,然后制定了客户端消息的业务处理逻辑 HelloClientHandler 对象调用 Bootstrap 类的 connect()方法进行连接,这个方法需要指定两个参数:inetHost : ip 地址 inetPort : 端口号
4、如何在 Netty 中解决 TCP 粘包问题?
1. 使用 Netty 自带的解码器解决粘包问题
LineBasedFrameDecoder : 发送端发送数据包的时候,每个数据包之间以换行符作为分隔, LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断是否有换行符,然后进行相应的截取。
DelimiterBasedFrameDecoder : 可以自定义分隔符解码器, LineBasedFrameDecoder 实际上是一种特殊的 DelimiterBasedFrameDecoder 解码器。
FixedLengthFrameDecoder: 固定长度解码器,它能够按照指定的长度对消息进行相应的拆包。
2. 自定义序列化编解码器
在 Java 中自带的有实现 Serializable 接口来实现序列化,但由于它性能、安全性等原因 一般情况下是不会被使用到的。通常情况下,我们使用 Protostuff、Hessian2、json 序列方式比较多,另外还有一些序 列化性能非常好的序列化方式也是很好的选择: 专门针对 Java 语言的:Kryo,FST 等等 跨语言的:Protostuff(基于 protobuf 发展而来),ProtoBuf,Thrift,Avro,MsgPack 等等
5、 Netty 的零拷贝
零复制(英语:Zero-copy;也译零拷贝)技术是指计算机执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。在 OS 层面上的 Zero-copy 通常指避免在用户态(User-space) 与内核态(Kernel- space) 之间来回拷贝数据。而在 Netty 层面 ,零拷贝主要体现在对于数据操作的优化。Netty 中的零拷贝体现在以下几个方面:使用 Netty 提供的 CompositeByteBuf 类, 可以将多个 ByteBuf 合并为一个逻辑上的ByteBuf, 避免了各个 ByteBuf 之间的拷贝。 ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的ByteBuf, 避免了内存的拷贝。通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题.
6、请说下 Netty 线程模型?
在 Netty 主要靠 NioEventLoopGroup 线程池来实现具体的线程模型的 。我们实现服务端的时候,一般会初始化两个线程组:bossGroup:接收连接。workerGroup :负责具体的处理,交由对应的 Handler 处理。
单线程模型 : 一个线程需要执行处理所有的 accept、read、decode、process、encode、send 事件。对于高负载、高并发,并且对性能要求比较高的场景不适用。
多线程模型 :一个 Acceptor 线程只负责监听客户端的连接,一个 NIO 线程池负责具体处理 accept、read、decode、process、encode、send 事件。满足绝大部分应用场景,并发连接量不大的时候没啥问题,但是遇到并发连接大的时候就可能会出现问题,成为性能瓶颈。
主从多线程模型:从一个 主线程 NIO 线程池中选择一个线程作为 Acceptor 线程,绑定监听端口,接收客户端连接的连接,其他线程负责后续的接入认证等工作。连接建立完成后,Sub NIO 线程池负责具体处理 I/O 读写。如果多线程模型无法满足你的需求的时候,可以考虑使用主从多线程模型。
7、EventloopGroup 了解么?和 EventLoop 啥关系?
EventLoopGroup 包含多个 EventLoop(每一个 EventLoop 通常内部包含一个线程), 上面我们已经说了 EventLoop 的主要作用实际就是负责监听网络事件并调用事件处理器进行相关 I/O 操作的处理。 并且 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理,即 Thread 和EventLoop 属于 1 : 1 的关系,从而保证线程安全。
Boss EventloopGroup 用于接收连接,Worker EventloopGroup 用于具体的处理(消息的读写以及其他逻辑处理)。当客户端通过 connect 方法连接服务端时,bossGroup 处理客户端连接请求。当客户端处理完成后,会将这个连接提交给 workerGroup 来处理,然后 workerGroup 负责处理其IO 相关操作。