Netty面试题

994 阅读8分钟

1. netty中 BIO NIO AIO的区别

IO模型就是网络数据传输过程中,使用什么通道去发送和接受数据

BIO同步阻塞,一个连接对应一个线程,如果server是单线程的,,后续链接会一直阻塞在那

NIO同步非阻塞,服务端开启了一个线程处理多个连接,所以他是非阻塞的,客户端发送的数据都会注册到多路复用器selector中,selector轮训到读写就绪,连接请求的时候,才会 主管发到后端线程去处理

NIO有三大组件:Channel、Buffer、Selector channel是双向读写数据的通道,buffer是存放数据的载体,是个缓冲区,selector对应一个或者多个channel, 客户端的链接都会注册到selector上面,然后由selector去调用后端处理程序

在NIO模型中,如果服务端执行了read操作,那么就说明已经有可用的数据进行读取了,执行accept操作,一定有客户端发起了与服务端的链接,能够保证这种操作的 前提是selector轮训到了可读可连接的通道状态

2. 如果seletor轮训到了多个read和accept状态,NIO是如何处理的

单线程 轮训得到多个变化的通道按照先后顺序同步处理,只有处理完上个通道,才能继续处理下一个 ,也就是redis的单线程IO模型

多线程 selector遍历得到一个可读可连接的通道,会交给服务端的一个线程去处理,自己会去处理下一个

poll相比select,没有最大连接的限制

epoll相对于前两者,是一个不一样的机制,基于事件通知的方式通知调用者

image.png

3. 简述netty线程模型

netty组件: ServerBootStrap、ServerBootStrap 是服务端和客户端启动配置的引导类,把所有的netty组件都串联起来

NioEventLoop 内部维护了一个线程和任务队列,支持异步提交任务,线程启动以后会调用loop的run方法,执行异步队列的非IO任务和IO任务

NioEventLoopGroup: 内部维护多个NioEventLoop,负责处理多个channel事件,但是一个channel只会交给一个NioEventLoop处理

IO任务:accept、connect、read、write 非IO任务:注册、绑定

ChannelHandler: 处在pipeline中没有出站和进站两种类型,服务端收到消息所有的进站handler都会执行

Netty通过Reactor模型基于多路复用器接收并处理用户请求,内部实现了两个线程池,boss线程池和work线程池,其中boss线程池的线程负责处理请求的accept事件,当接收到accept事件的请求时,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责请求的read和write事件,由对应的Handler处理。

单线程模型:所有I/O操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor线程上完成的。既要接收客户端的连接请求,向服务端发起连接,又要发送/读取请求或应答/响应消息。一个NIO 线程同时处理成百上千的链路,性能上无法支撑,速度慢,若线程进入死循环,整个程序不可用,对于高负载、大并发的应用场景不合适。

多线程模型:有一个NIO 线程(Acceptor) 只负责监听服务端,接收客户端的TCP 连接请求;NIO 线程池负责网络IO 的操作,即消息的读取、解码、编码和发送;1 个NIO 线程可以同时处理N 条链路,但是1 个链路只对应1 个NIO 线程,这是为了防止发生并发操作问题。但在并发百万客户端连接或需要安全认证时,一个Acceptor 线程可能会存在性能不足问题。

主从多线程模型:Acceptor 线程用于绑定监听端口,接收客户端连接,将SocketChannel 从主线程池的Reactor 线程的多路复用器上移除,重新注册到Sub 线程池的线程上,用于处理I/O 的读写等操作,从而保证mainReactor只负责接入认证、握手等操作;

4. 什么是粘包和拆包

应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据合并发出,发生粘包

进行MSS大小的TCP分段,当TCP报文长度- TCP头部长度 > MSS 的时候将发生拆包, 以太网的payLoad载荷 大于MTU 进行ip分片

解决手段

固定长度 FixedLengthFrameDecoder

自定义分隔符 DelimiterBasedFrameDecoder

行分隔符 LineBasedFrameDecoder

5. 什么是netty以及他的特性

异步事件驱动的

基于NIO的

网络通信框架

特性:

高并发,NIO,同步非阻塞的

传输快,依赖于零拷贝

封装好,基于NIO封装,好用

6. 简述netty的零拷贝

netty中的零拷贝和操作系统的零拷贝不一样,操作系统的零拷贝主要是内核态的优化,netty是在用户态实现零拷贝

CompositeByteBuf 实现零拷贝, 通过将多个ByteBuf合并成一个ByteBuf,避免了各个ByteBuf之间的拷贝

wrap实现零拷贝, 将byte[] 数组包装成一个byteBUf对象避免拷贝操作

slice 实现零拷贝, 将ByteBuf 拆解成多个ByteBuf,但是共享同一存储空间不同分区,避免内存拷贝

通过FileRegion 包装FileChannel.transferTo 实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel避免了传统循环write内存拷贝的问题

7. 简述netty的线程模型

目前存在的线程模型 传统阻塞IO模型、 Reactor模式

根据reactor的数量和处理资源线程的数量不同,分为3种

单Reactor单线程

单Reactor多线程

主从Reactor多线程

netty是基于主从Reactor多线程的

8. netty编程涉及到的IO优化参数

bossGroup表示监听端口,accept 新连接的线程组,workerGroup表示处理每一条连接的数据读写的线程组 ChannelOption.SO_BACKLOG,表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数

ChannelOption.CONNECT_TIMEOUT_MILLIS 表示连接的超时时间,超过这个时间还是建立不上的话则代表连接失败

ChannelOption.SO_KEEPALIVE 表示是否开启 TCP 底层心跳机制,true 为开启

ChannelOption.TCP_NODELAY 表示是否开始 Nagle 算法,true 表示关闭,false 表示开启, 通俗地说,如果要求高实时性,有数据发送时就马上发送,就设置为 true 关闭,如果需要减少发送次数减少网络交互,就设置为 false 开启

9. 协议栈设计时候的魔数的作用

有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。

在 Java 的字节码的二进制文件中,开头的 4 个字节为0xcafebabe 用来标识这是个字节码文件,亦是异曲同工之妙。

10. 序列化和编解码的关系

序列化是把我们的请求体序列化成字节数组

编解码时按照协议栈的格式组装ByteBuf,然后writeAndFlush

11. 如何创建一个byteBuf

ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer() 这里我们调用 Netty 的 ByteBuf 分配器来创建,ioBuffer() 方法会返回适配 io 读写相关的内存,它会尽可能创建一个直接内存,直接内存可以理解为不受 jvm 堆管理的内存空间,写到 IO 缓冲区的效果更高 ctx.alloc() 获取的就是与当前连接相关的 ByteBuf 分配器

12. super.channelRead(ctx,msg) 与ctx.fireChannelRead(msg)的区别

ChannelInboundHandlerAdapter 里面channelRead用super.channelRead(ctx, msg);

ChannelOutboundHandlerAdapter 里面write用super.write(ctx, msg, promise);

13. 怎么判断bytebuf是堆外内存?

ByteBuf.isDirect()

14. LengthFieldBasedFrameDecoder解码器的含义

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 7, 4); 其中,第一个参数指的是数据包的最大长度,第二个参数指的是长度域的偏移量,第三个参数指的是长度域的长度,这样一个拆包器写好之后,只需要在 pipeline 的最前面加上这个拆包器。

15. ChannelHandler 回调方法的执行顺序

handlerAdded() -> channelRegistered() -> channelActive() -> channelRead() -> channelReadComplete()

客户端关闭ChannelHandler 回调方法的执行顺序

channelInactive() -> channelUnregistered() -> handlerRemoved() IdleStateHandler 配置0是啥意思

handlerAdded() 与 handlerRemoved()

这两个方法通常可以用在一些资源的申请和释放

channelActive() 与 channelInActive()

对我们的应用程序来说,这两个方法表明的含义是 TCP 连接的建立与释放,通常我们在这两个回调里面统计单机的连接数,channelActive() 被调用,连接数加一,channelInActive() 被调用,连接数减一

另外,我们也可以在 channelActive() 方法中,实现对客户端连接 ip 黑白名单的过滤,具体这里就不展开了

啥时候触发ChannelInActive

16 参考

juejin.cn/post/684490…

juejin.cn/post/699922…