Netty面试重点-1

1. Netty是什么?

概念:**

Netty是一个异步事件驱动的网络通信框架,用于开发高性能协议的服务器和客户端。Netty是基于NIO的,它封装了JDK的NIO,让我们使用起来更加方法灵活。

特点:

  • 高并发:基于NIO开发的网络通信框架,比BIO的并发性提高很多。
  • 传输快:传输依赖于零拷贝,使用高性能的序列化框架。
  • 封装好:对NIO操作的很多细节进行了封装,提供了易于使用调用接口。

优势:

  • API使用简单,开发门口低:封装了NIO的很多细节。
  • 功能强大:预置了多种编解码功能,支持多种主流协议。
  • 定制能力强:可以通过ChannelHandler对通信框架进行灵活地扩展。
  • 性能高、稳定、社区活跃。

应用场景:-> 作为基础网络通信框架

  • RPC框架的基础网络通信框架,例如Dubbo等。
  • 私有共有协议的基础网络通信框架,例如Dubbo协议、Http协议等。
  • 游戏行业,使用Netty作为高性能的基础通信组件。
  • RocketMQ使用Netty作为通信组件。

2. Netty的高性能表现: **

  • 采用异步非阻塞的IO类库(NIO),使用Reactor异步通信模型。
  • TCP的缓冲区使用直接内存代替堆内存,避免了内存复制。
  • 内存池的设计,循环利用ByteBuf,避免了频繁创建和销毁ByteBuf带来的性能损耗。
  • 可配置的IO线程数、TCP参数等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。
  • 零拷贝(3种)。
  • 无锁化设计:
    • 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的CPU资源消耗问题:IO线程(EventLoop线程)内部单线程串行化处理
    • 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。
  • 高性能的序列化框架。
  • 高效的并发编程。

3. Netty的可靠性表现:

  • 网络通信类故障的解决:
    • Netty支持配置客户端的超时时间,NIO类库没有提供现成的链路超时接口,Netty自己封装的。
    • 通信对端强制关闭连接,Netty在IO异常之后,会调用关闭连接。
    • 链路关闭:Netty在read操作返回-1后,调用方法关闭句柄,释放资源。
    • 定制IO故障处理:Netty支持用户自定义处理IO异常,Netty具体实现的接口:ChannelHandlerAdapter.exceptionCaught()。
  • 链路有效性检测:Netty采用链路空闲时心跳检测机制,保证长链接的链路有效性。
  • Reactor线程的保护:(最好不说)
    • 例如,Netty策略规避了epoll bug
  • 内存保护机制:
    • Netty提供了内存池和对象池,提升内存的利用率。
    • 可设置的内存容量上限,包括:ByteBuf、线程池线程数等。
  • 优雅停机:
    • Netty5.0版本开始优雅退出功能,做得更加完善。

4. NIO的相关知识:****(@&@)

NIO的特点:

  • 非阻塞I/O,I/O读写不再阻塞,而是返回0。
  • IO多路复用,提高了Java网络应用的可伸缩性和实用性。
  • 事件驱动模型。
  • 单线程处理多任务。
  • 基于Block的传输比基于流的传输更高效。
  • 更高级的IO函数,例如zero-copy。

原生NIO的问题:

  • NIO的类库和API繁杂,使用麻烦。
  • 需要具备额外的技能做铺垫。例如:熟悉Java多线程编程(涉及到Reactor模式),熟悉网路编程。
  • 可靠性能力需要补齐,开发工作量和难度都非常大。
  • JDK NIO的BUG。例如:臭名昭著的epoll bug,它会导致Selector空轮询, 最终导致CPU 100%。

NIO的服务端建立过程:**

  • Selector.open():打开一个Selector -> epoll_create -> fd3。
  • ServerSocketChannel.open():创建服务端的Channel。
  • configureBlocking(false):设置非阻塞。
  • bind():绑定到某个端口上。
  • register():在Channel中注册此Selector -> epoll_ctl(fd3,ADD,fd7,EPOLLIN。
  • select():轮询拿到已经就绪的事件 -> epoll_wait()。
  • 处理新接入的客户端链接 或 处理成功链接客户端的传输数据。

Buffer(缓冲区):

  • 概念:
    • 它与Channel进行交互,数据是从管道(Channel)读入缓冲区(Buffer),从缓冲区写入管道中。
    • 它本质是一个数组,最常用的是一个字节数组,即ByteBuffer。
  • 方法:
    • put():写入缓冲区。
    • get():获取缓存区数据。
    • flip() : 反转此缓冲区,将position给limit,然后将position置为0,其实就是切换读写模式 。【福累普】
    • clear() :清除此缓冲区,将position置为0,把capacity的值给limit。
    • rewind() : 重绕此缓冲区,将position置为0 。
  • DirectBuffer(直接缓冲区):
    • 它可以减少一次系统空间到用户空间的拷贝。但是,直接缓存区的创建和销毁成本更高,不可控,通常会用内存池来提高性能。

    • 直接缓冲区主要分配给那些易受基础系统的本机I/O 操作影响的大型、持久的缓冲区。如果数据量比较小的中小应用,可以考虑使用堆内缓冲区,由JVM进行管理。

      // 使用直接内存,堆外内存,即JVM的堆外
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      // 使用直接内存,堆外内存,即JVM的堆外
      ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

Channel(通道):

  • NIO通过Channel进行读写。

  • Channel是双向的,可读可写,但不能直接访问数据,只能与Buffer进行交互。

  • FileChannel的read()和write()都导致数据复制了两次,首先复制到Buffer,然后再从Buffer中进行读或写。

    // 打开服务端 socket channel
    ServerSocketChannel serverChannel= ServerSocketChannel.open();
    // 打开客户端 socket channel
    SocketChannel clientChannel = SocketChannel.open();

Selector(多路复用器):

  • 概念:
    • Selector管理多个Channel,它会不断轮询已经注册的Channel,如果某个Channel上发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来。
  • 方法:
    • open():创建Selector。
    • register():向Selector注册Channel,可以监听的事件类型:读、写、连接、accept。注册事件后会产生一个SelectionKey:它表示SelectableChannel和Selector之间的注册关系。
    • select():阻塞到至少有一个通道在你注册的事件上就绪。
    • wakeup():使尚未返回的第一个选择操作立即返回,唤醒的原因是:注册了新的Channel或者事件;Channel关闭或取消注册;优先级更高的事件触发(如定时器事件),希望及时处理。

5. Netty 的组成(重要组件):***

Bootstrap、ServerBootstrap(启动导引类):

  • Bootstrap类是客户端的启动引导类,ServerBootstrap是服务端的启动引导类。
  • 它的作用方便配置整个Netty程序,串联各个组件,降低开发复杂度。
  • handler()和childHandler()的区别:
    • handler()是发生在初始化的时候,childHandler()是发生在客户端连接之后。

ByteBuf:**

  • 概念:字节数组缓冲区。
  • 特点:不需要读写切换(数据结构增加readerIndex、writerIndex),容量可以动态调整,API功能丰富(Discardable bytes、随机读写、支持多种查找操作)。
  • 分类:直接内存与堆内存、普通内存与内存池。
  • 重点知识:内存池、对象池。

Channel:**

  • 概念:Netty网络的抽象类,主要包括网络的读、写,客户端发起链接、主动关闭、链路关闭等,它还包含了Netty框架的一些功能,例如:获取该Channel的EvenLoop、获取缓冲分配器(ByteBufAllocator)和Pipeline等。
  • 生命周期(四种状态):channelUnregistered、channelRegistered、channelActive、channelInactive
  • 常用的Channel类型:
    • NioSocketChannel:异步的客户端TCP Socket连接。
    • NioServerSocketChannel:异步的服务器端TCP Socket连接。
    • NioDatagramChannel:异步的UDP连接。
    • NioSctpChannel:异步的客户端Sctp连接。
    • NioSctpServerChannel:异步的Sctp服务器端连接,这些通道涵盖了UDP和TCP网络 IO以及文件IO。

Unsafe:

  • 概念:它是Channel接口的辅助接口。实际的IO读写操作都是由Unsafe接口负责完成的。

ChannelPipeline:**

  • Netty将Channel的数据管道抽象为ChannelPipeline,消息在CP中流动和传递。
  • 它是ChannelHandler链的容器,负责CH的管理、事件的拦截与调度。
  • 它底层是一个双向链表,它并不直接存储的CH,而是CHC,在CHC可以直接获取到与之对应的CH、CP、Channel。
  • 重要知识:
    • 它的本质是一个负责处理网络事件的责任链。
    • 它是线程安全的,底层使用synchronized。
    • 入站(fireXXX命名的方法)与出站概念。

Tips:ChannelPipeline简称CP、ChannelHandler简称CH、ChannelHandlerContext简称CHC

ChannelHandler:**

  • 概念:类似于Filter过滤器,对IO事件或者IO操作进行拦截和处理,它可以选择性地拦截或透传。
  • Netty框架自己实现许多的Handler,例如编解码的、解决TCP半包粘包的、心跳检测的ChannelHandlerAdapter等。
  • 重要知识:
    • 它不是线程安全的,需要自己保证线程安全。

ChannelHandlerContext:*

它保存Channel相关的所有上下文信息,同时关联一个CH对象和CP对象。

NioEventLoopGroup:**

  • 概念:主要管理EventLoop的生命周期,它本质是一个Reactor线程池,它负责调度和执行客户端接入、网络读写事件的处理、用户自定义任务和定时任务的执行。

NioEventLoop:**

  • 概念:处理IO操作的线程,EventLoop是Reactor线程模型的事件处理引擎,每个EventLoop线程都维护一个Selector选择器和任务队列taskQueue。它主要负责处理 I/O 事件、普通任务和定时任务。
  • I/O任务和非I/O任务:
    • I/O任务,即SelectionKey中ready的事件,如accept、read、write等,由processSelectedKeys()触发。
    • 非IO任务,添加到taskQueue中的任务,如register、bind等任务,由runAllTasks()触发。
    • 两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。
  • 重要知识:事件循环、无锁化串行处理。

Selector:**

Netty基于Selector对象实现I/O多路复用。

ChannelFuture:

  • 概念:它与JDK的Future类似,主要作用是异步操作的通知回调,因为Netty框架中所有的I/O操作都为异步的。
  • 两种状态:uncompleted(表示非失败、非成功、非取消)、completed(操作成功、操作失败、操作被取消)。

Promise:

  • 概念:Promise是可写的Future,Future自身并没有写操作相关接口,Netty通过Promise对Future进行扩展,用于设置IO操作的结果。

6. Netty的工作架构图(Server端):

Server端包含一个Boss和一个Worker的NioEventLoopGroup。NioEventLoopGroup相当于1个事件循环组,它包含多个事件循环NioEventLoop,每个NioEventLoop包含1个selector和1 个事件循环线程。

每个Boss NioEventLoop循环执行的任务包含3步:

      1. 轮询accept事件;
      1. 处理accept I/O事件,与Client建立连接,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上;
      1. 处理任务队列中的任务(runAllTasks)。

每个Worker NioEventLoop循环执行的任务包含3步:

      1. 轮询read、write事件;
      1. 处理I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生 时进行处理;
      1. 处理任务队列中的任务(runAllTasks)。

7. ByteBuf中unpool与pool:-> 内存池 ****(@&@)

总:

  • unpool与pool是Netty中,内存池的相关概念。Netty使用内存池可以有效的利用内存、减少创建和销毁内存的开销、减少内存碎片化(jemalloc算法)等。
  • unpool表示没有使用内存池的缓冲区,pool表示使用内存池的缓冲区。
  • 内存池可以分为堆内内存池和直接内存池(即PollByteBuf 、PoolDirectByteBuf)。
  • 这块涉及的知识点:jemalloc【G 马姥渴】算法、内存规格、内存池结构、内存分配原理、内存回收原理。

分:

Jemalloc算法:

  • 该算法主要通过Arean【饿瑞嗯】和Thread Cache技术,提高多线程下内存分配的效率。
    • Arean:分而治之的思想,将任务派发给多个人,每个人单独管理,互不干涉。
    • Thread Cache机制:每个线程私有缓存,内存分配在自己的线程内,不与其它线程竞争。

内存规格:

  • 分类:Tiny [0B,512B)、Small [512B,8K) 、Normal [8K,16M]、 Huge (16M, 无限大)。
  • 默认是Normal类型,16MB。
  • 内存大于16MB(即Huge类型),不做缓存、不做池化,直接以Unpool的形式分配内存,用完后回收。

内存池结构:

  • PoolArena:
    • 指向系统或JVM堆申请一块内存区域,与Memory Arena概念的类似。
    • Netty会固定分配多个Arena,Arena默认数量与CPU 核数有关。
    • 它包含两个PoolSubpage类型的数组(分别存放Tiny和Small 类型的内存块)和六个PoolChunkList(分别存储不同利用率的Chunk),构成一个双向循环链表。
  • PoolChunkList:
    • 它负责管理多个PoolChunk的生命周期,同一个ChunkList中存放内存使用率相近的Chunk,Chunk以双向链表的形式连接在一起。
    • 每个PoolChunkList上下线有重叠,避免因为内存使用率在临界值,一直移动,造成性能损耗。
  • PoolChunk:
    • Netty内存的分配和回收都是基于PoolChunk完成的,PoolChunk是真正存储内存数据的地方。
    • PoolChunk的默认大小为16M。PoolChunk可以理解为Page的集合,一个PoolChunk分配成2048个Page,最终形成一颗满二叉树。
  • PoolPage:
    • PoolPage是PoolChunk用于管理内存的基本单位。
    • 每个Page默认大小是8K。
  • PoolSubPage:
    • 在小内存分配的场景下(内存小于8K),会使用PoolSubpage进行管理。
  • PoolThreadCache:
    • Netty会为每一个线程都维护一个PoolThreadCache对象,当进行内存申请时,首先会尝试从PoolThreadCache中申请,如果无法从中申请到,则会尝试从Netty的公共内存池中申请。

内存分配原理:

  • Netty中负责线程分配的组件有两个:PoolArena和PoolThreadCache。PoolArena是多个线程共享的,每个线程会固定绑定一个 PoolArena,PoolThreadCache是每个线程私有的缓存空间。
  • Netty根据不同内存规格采用不同的分配策略:
    • 分配内存大于8K时,PoolChunk中采用的Page级别的内存分配策略。
    • 分配内存小于8K时,由PoolSubpage负责管理的内存分配策略。
    • 分配内存小于8K时,为了提高内存分配效率,由PoolThreadCache本地线程缓存提供的内存分配。

内存回收原理:

  • Netty在分配内存会记录执行次数,默认每执行8192次,就会触发PoolThreadCache调用一次trim()进行内存整理,释放调没有被使用的内存区域。

8. Netty的对象池:***(@&@)

总:

  • 为了避免过多的创建对象和频繁的GC,才使用了对象池。
  • Netty缓冲区的实现类,通过聚合的方式引入Recycler类【瑞 塞渴乐】。在申请缓冲区时,从Recycler中获取,如果没有再创建。在缓冲区使用完毕后,也会被回收到Recycler中,方便下次使用时,直接从Recycler中获取。

分:

Recycler类的简单介绍(Netty 4.X):

  • 核心方法:get(获取)、recycle(回收)、newObject(创建新对象)
  • 核心属性:
    • DefaultHandle:对象的包装类,在Recycler中缓存的对象都会包装成DefaultHandle类。
    • Stack: 存储本线程的回收对象 。
    • WeakOrderQueue:存储其它线程回收到本线程Stack的对象。
    • Link:WeakOrderQueue中包含1个Link链表,回收对象存储在链表某个Link节点里。Link节点存储回收对象时,会新建1个Link放在Link链表尾。
  • 获取和回收流程:
  • Recycler的无锁化的思考 -> 见《Netty权威指南》
  • Netty5.X去掉了WeakOrderQueue结构。

9. Inbound事件与Outbound事件区别:***

Inbound事件(入站事件):

  • 它通常由IO线程触发,例如,TCP链路建立事件、链路关闭事件、读事件、异常通知事件等。
  • 入站事件在ChannelHandler链执行顺序:从头到尾,顺序执行。
  • 触发Inbound事件的方法(都是fire开头):
    • ChannelHandlerContext.fireChannelRegistered():Channel注册事件。
    • CHC.fireChannelActive():TCP链路建立成功,Channel激活事件。
    • CHC.fireChannelRead(Object):读事件。
    • CHC.fireExceptionCaught(Throwable):异常通知事件。
    • .... ....

Outbound事件(出站事件):

  • 它通常是由用户或代码主动发起网络IO操作,例如,用户发起的连接操作、绑定操作、消息发送操作等。
  • 出站事件在ChannelHandler链执行顺序:从尾到头,逆序执行。
  • 触发Outbound事件的方法:
    • ChannelHandlerContext.bind(SocketAddress,ChannelPromise):绑定本地地址事件。
    • CHC.connect(SocketAddress,SocketAddress,ChannelPromise):连接服务端事件。
    • CHC.write(Object,ChannelPromise):发送事件。
    • CHC.flush():刷新事件。
    • CHC.read() 读事件。
    • .... ....

InboundHandler和OutboundHandler的区别?

  • InboundHandler顺序执行(头到尾),OutboundHandler逆序执行(尾到头)。
  • InboundHandler之间传递数据,通过ctx.fireChannelRead(msg)。
  • InboundHandler通过ctx.write(msg),则会传递到OutboundHandler。
  • OutBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先Outbound再Inbound,服务端则相反。

10. Netty 支持哪些心跳类型设置?

  • Netty利用链路空闲检测机制实现的心跳检测。
  • Netty空间检测机制的分类:读空闲、写空闲、读写空闲。
  • Netty自己实现的Handler:
    • IdleStateHandler(三种类型都检测,在超时之后,触发用户Handler的userEventTriggered)
    • ReadTimeoutHandler(读超时,抛异常并且断开链接)
    • WriteTimeoutHandler(写完超时,抛异常并且断开链接,Tips:IdleStateHandler是发生写超时)
  • Netty有IdleStateEvent事件,用户可以订阅该事件,自定义相关的逻辑。

11. Reactor事件驱动线程模型:

概念:

  • 它是一种IO模型,具体说是事件驱动的IO模型。它是异步操作的。
  • Reactor线程模型是I/O多路复用 + 线程池的结合。

模型角色(3个):

  • Acceptor【饿渴赛破特儿】:处理客户端连接事件。
  • Reactor:把IO事件分配给对应的handler处理,功能像是调度器。
  • Handler:处理具体的任务。

运行机制(4步):

  • 连接注册:Channel建立后,注册到Reactor线程的Selector中。
  • 事件轮询:轮询Selector选择器中,已注册的所有Channel的IO事件。
  • 事件分发:将准备就绪的 IO 事件分配给相应的处理线程。
  • 任务处理:Reactor线程还负责任务队列中的非 IO 任务,每个Worker线程从各自维护的任务队列中,取出任务异步执行。

模型分类(3类):

  • Reactor单线程模型:所有的IO操作在同一个NIO线程上完成;小容量场景可以,但是高并发场景不合适,一个NIO线程处理上千个链路扛不住。
  • Reactor多线程模型:有专业用于监听客户端链接请求的NIO线程(即Acceptor线程)和专业处理IO操作的线程池(即Reactor Pool);绝大多数场景可以,但是一个Acceptor线程在高并发下,也会存在性能问题。
  • 主从Reactor多线程模型:接受客户端链接请求的是一个NIO线程池(即Acceptor Pool),处理IO操作的也是一个线程池(即Reactor Pool)。

12. Netty的线程模型? ***

概念:

  • Netty可以支持Reactor单线程、多线程和主从多线程模型,用户可以通过启动参数进行配置。
  • Netty服务端启动时,创建了两个NioEventLoopGroup,它实际是两个独立的线程池。一个用于接收客户端的TCP连接(Boss),另一个用于处理IO相关的读写操作,或者执行系统Task、定时任务Task等(Work)。
    • 用于接收客户端请求线程池的主要职责(Acceptor线程池,即Boss线程池):
      • 接收客户端TCP连接,初始化Channel参数,即处理客户端链接请求的accept事件。
      • 将链路状态变更事件,通知给ChannelPipeline。
    • 用于处理IO操作线程池的主要职责(Reactor线程池,即Work线程池):
      • 异步读取通信对端的数据报,发送读事件到ChannelPipeline。
      • 异步发送消息到通信对端,调用ChannelPipeline的消息发送接口。
      • 执行系统调用Task。
      • 执行定时任务Task,例如,链路空闲状态监测定时任务。
  • Netty一般使用Reactor主从多线程模型。

模式:

  • 单线程模型
  • 多线程模型:
    • 创建两个NioEventLoopGroup,bossGroup大小设置为1,workGroup大小设置为CPU*2(或自定义一下)。
  • 主从多线程模型:

13. EventLoop与事件循环机制:

概念:

  • EventLoop不是Netty独有的概念,它是一种事件等待和处理的程序模型,可以解决多线程资源消耗高的问题。例如Node.js也采用了EventLoop的运行机制。
  • 在Netty中,EventLoop是Reactor线程模型的事件处理引擎,每个EventLoop线程都维护一个Selector选择器和任务队列taskQueue。
  • EventLoop主要负责处理 I/O 事件、普通任务和定时任务。
  • EventLoop无锁串行化的设计,好处使系统吞吐量达到最大化,而且降低了用户开发业务逻辑的难度,不需要花太多精力关心线程安全问题。

  • 事件循环机制就是通过一个死循环,不断处理当前已发生的I/O事件和待处理的异步任务。

NioEventLoop循环处理的流程:

NioEventLoop每次循环的处理流程都包含:事件轮询(select)、事件处理(processSelectedKeys)、任务处理(runAllTasks)这个几个步骤,是典型的Reactor线程模型的运行机制。而且,Netty提供了一个参数 ioRatio,可以调整 IO 事件处理和任务处理的时间比例。

14. Netty的零拷贝(3种):**

  • Netty的接收和发送采用直接缓存区,使用堆外直接内存进行Socke读写,不需要进行字节缓冲区的二次拷贝。
  • Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,可以对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
  • Netty的文件传输采用了transferTo(),它可以直接将缓冲区的数据发送到目标Channel,避免了传统通过循环Write方式的内存拷贝问题。

15. 代码如果保证ChannelHandler的线程安全(2点)?

  • 保证每个Channel尽量都拥有自己的Handler,而不是Handler在多个Channel之间共享。
    • 推荐这种写法:ch.pipeline().addLast(new ServerHandler())
  • 尽量不使用共享资源,如果使用了共享资源,在操作时需要对共享资源进行同步,如加锁。
相关推荐
刀客1232 小时前
C++ 面试总结
开发语言·c++·面试
yenggd2 小时前
sr mpls te隧道配置案例
网络·华为
序属秋秋秋3 小时前
《C++进阶之C++11》【智能指针】(下)
c++·笔记·学习·面试·c++11·智能指针·新特性
锋风Fengfeng3 小时前
基于Binder的4种RPC调用
网络协议·rpc·binder
序属秋秋秋3 小时前
《C++进阶之C++11》【智能指针】(上)
c++·笔记·学习·面试·c++11·智能指针·新特性
skywalk81634 小时前
调试parlant的大模型配置,最终自己动手写了g4f的模块挂载
网络·人工智能·语言模型·tiktoken
FFFfengZiz.5 小时前
HTTP相关
网络·网络协议·http
网安小白的进阶之路5 小时前
A模块 系统与网络安全 第四门课 弹性交换网络-3
网络·安全·web安全
源文雨7 小时前
MacOS 下 Warp ping 局域网设备报错 ping: sendto: No route to host 的解决方法
运维·网络协议·安全·macos·网络安全·ping