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步:
-
-
- 轮询accept事件;
-
- 处理accept I/O事件,与Client建立连接,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上;
-
- 处理任务队列中的任务(runAllTasks)。
-
每个Worker NioEventLoop循环执行的任务包含3步:
-
-
- 轮询read、write事件;
-
- 处理I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生 时进行处理;
-
- 处理任务队列中的任务(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())
- 尽量不使用共享资源,如果使用了共享资源,在操作时需要对共享资源进行同步,如加锁。