Netty面试题汇总

BIO、NIO和AIO的区别

  • BIO:一个连接一个线程,客户端有链接请求时服务端就需要启动一个线程进行处理,线程开销大。
  • 伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。
  • NIO:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
  • AIO:一个有效请求一个线程,客户端I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
  • BIO是面向流的,NIO是面向缓冲区的;BIO的各种流是阻塞的,而NIO是非阻塞的;BIO的流是单向的,而NIO的channel是双向的。
  • NIO的特点:事件驱动模型,单线程处理多任务,非阻塞I/O,I/O读写不再阻塞。基于block的传输比基于流的传输更高效。更高级的IO函数zero-copy。IO多路复用大大提高了网络应用的可伸缩性和实用性。基于Reactor线程模型。
  • 在Reactor模型中,事件分发器等待某个事件或者应用操作的状态发生,事件分发器就把这个事件传给已注册的事件处理函数或者回调函数,由后者来做实际的读写操作。如在Reactor中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件,事件到来激活分发器,分发器调用事件对应的处理器,事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返回控制权。

Netty的特点

  • 一个高性能,异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持;
  • 使用更高效的socket底层,对epoll空轮询引起的CPU占用飙升在内部进行了处理,避免了直接使用NIO的陷阱,简化了NIO的处理方式;
  • 采用多种decoder/encoder支持,对TCP粘包/分包进行自动化处理;
  • 可使用接受/处理线程池,提高连接效率,对重连、心跳监测的简单支持;
  • 可配置IO线程数,TCP参数,TCP接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用ByteBuf;
  • 通过引用计数器及时申请释放不再引用的对象,降低了GC频率
  • 使用单线程串行化的方式,高效的Reactor线程模型
  • 大量使用了volitale、使用CAS和原子类,线程安全类的使用,读写锁的使用

Netty的线程模型

  • Netty通过Reactor模型基于多路复用器接收并处理请求,内部实现了两个线程池,boss线程池和work线程池。其中boss线程池的线程负责处理请求的accept事件,当接收到accept事件的请求时,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责请求的read和write事件,由对应的Handler处理。
  • 单线程模型:所有I/O操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor线程上完成的。既要接收客户端的连接请求,向服务端发起连接,又要发送读取请求或应答响应消息。一个NIO线程同时处理成百上千个链路,性能上无法支撑,速度慢,若线程进入死循环,整个程序不可用,对于高负载、大并发的应用场景不合适。
  • 多线程模型:有一个NIO线程(Acceptor)只负责监听服务端,接收客户端的TCP连接请求;NIO线程池负责网络IO的操作,即消息的读取、解码、编码和发送;一个NIO线程可以同时处理N条链路,但是一个链路只对应一个NIO线程,这是为了防止发生并发操作问题。但在并发百万客户端连接或需要安全认证时,一个Acceptor线程可能会存在性能不足问题。
  • 主从多线程模型:Acceptor线程用于绑定监听端口,接收客户端连接,将SocketChannel从主线程池的Reactor线程的多路复用器上移除,从新注册到Sub线程池上,用于处理I/O的读写等操作,从而保证mainReactor只负责接入认证、握手等操作;

TCP粘包/拆包的原因及解决方法

TCP粘包/分包的原因:TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。

  • 应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据一次发送到网络上,这将会发生粘包现象;
  • 进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包;
  • 以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。

解决方法:

  • 消息定长:FixedLengthFrameDecoder类
  • 包尾增加特殊字符分割,行分隔符类:LineBasedFrameDecoder或自定义分隔符类:DelimiterBasedFrameDecoder
  • 将消息分为消息头和消息体:LengthFieldBasedFrameDecoder类。分为有头部的拆包和粘包,长度字段在前且有头部的拆包和粘包,多扩展头部的拆包和粘包

Netty零拷贝实现

Netty的接收和发送ByteBuffer采用Direct Buffers,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。堆内存多了一次内存拷贝,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。ByteBuffer由ChannelConfig分配,而ChannelConfig创建ByteBufAllocator默认使用Direct Buffer。

  • CompositeByteBuf 类可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。addComponents方法将 header 与 body 合并为一个逻辑上的 ByteBuf, 这两个 ByteBuf 在CompositeByteBuf 内部都是单独存在的, CompositeByteBuf 只是逻辑上是一个整体

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

  • 通过 wrap方法, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作。

Selector BUG

若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%。

Netty的解决办法:对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值