一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅知识面试题,也是你 Java 知识点的扫盲贴。
回答Netty的高性能主要表现在以下几个方面。异步非阻塞IONetty是基于JavaNIO的使用异步非阻塞IO模式进行网络通信,能够支持更多连接,提高系统的吞吐量和可伸缩性。你知道几种I/O模型?BIO、NIO和AIO的区别?事件驱动机制Netty采用事件驱动机制,基于Reactor模式设计。Netty将I/O事件的接收、读、写、连接、断开等操作封装为事件,由事件循环(EventLoop)负责分发给对应的处理器。这种模式使得事件的处理更加高效。请说下你对Netty中Reactor模式的理解你是如何理解Netty的线程模型?零拷贝技术Netty利用零拷贝技术减少数据在内存中复制的次数,从而减少CPU的拷贝操作,提高数据处理速度。例如,Netty的CompositeByteBuf类可以将多个ByteBuf合并为一个逻辑上的ByteBuf,在不实际移动数据的情况下,作为一个整体被发送或接收。谈谈
回答Netty精心设计和实现的内存模型是Netty高性能的核心原因之一。Netty的内存模型核心围绕ByteBuf、ByteBufAllocator、零拷贝、以及基于jemelloc实现的内存分配算法来展开的。ByteBuf:Netty引入ByteBuf来作为在网络中传输数据的载体,取代JavaNIO的ByteBuffer。相比ByteBuffer,ByteBuf通过读写索引分离来提供更加高效的操作,通过引用计数机制和池化技术来提高内存使用效率。ByteBufAllocator:负责ByteBuf的内存分配,包括池化和非池化两种分配器,其中池化分配器通过预分配和重用内存块来减少内存分配的开销,并通过内存分割和子分配策略来优化内存管理,减少碎片。零拷贝:Netty利用ByteBuf的CompositeByteBuf和slice()来实现零拷贝技术。基于jemelloc的内存分配算法:扩展关于
回答Netty是支持使用SSL/TLS协议进行加密传输的,其主要依赖SslHandler。SslHandler是Netty内置的一个处理加密和解密ChannelHandler,使用它我们可以很方便地在Netty中添加SSL/TLS加密功能,确保数据传输的安全性。主要分为三个步骤:生成密钥和证书创建SslContext实例将SslHandler添加到ChannelPipeline详解在Netty中使用SslHandler实现SSL/TLS加密传输分为三步。生成密钥和证书首先我们需要生成一个SSL证书。SSL全称是SecureSocketsLayer,安全套接字层,它是由网景公司(Netscape)设计的主要用于Web的安全传输协议,目的是为网络通信提供机密性、认证性及数据完整性保障。如今,SSL已经成为互联网保密通信的工业标准。SSL/TLS位于TCP层和应用层之间,具体如下图所示:生成SS
回答大文件的传输是不能一次性加载整个文件到内存中的,因为这样会导致内存溢出或严重的性能问题。对于大文件的传输我们一般有两种常见的处理方式。零拷贝传输利用零拷贝技术来优化大文件传输,它是利用操作系统内核的文件映射机制,将文件的内容映射到进程的地址空间中减少了数据在内核空间和用户空间之间的拷贝次数。在Netty中,我们可以使用FileRegion来实现零拷贝方式的文件传输。FileRegion可以直接将文件内容写入到SocketChannel中,而不需要将文件内容复制到缓冲区中。分片传输整个大文件无法传输,那我们可以将文件切片,即将大文件分成多个小段,然后分别传输。在Netty中,我们可以使用ChunkedWriteHandler处理器。该处理器会将大文件切割成多个小块,然后逐个异步发送。扩展ChunkedWriteHandlerChunkedWriteHandler是Netty中用于处理大型
回答ChannelHandlerContext代表了当前ChannelHandler在ChannelPipeline中的上下文信息。它的主要作用是提供了ChannelHandler与其所属的ChannelPipeline之间的交互。简单来说,就是ChannelHandlerContext允许一个ChannelHandler与其所在的ChannelPipeline以及其他的ChannelHandler互动。在Netty中,每个Channel都有一个与之对应的ChannelPipeline,这个ChannelPipeline包含了多个ChannelHandler。数据在ChannelPipeline上流通,ChannelHandler则对数据进行加工或者转发,而ChannelHandlerContext则扮演着桥梁的角色,它连接ChannelHandler和ChannelPipeline,提供
回答ChannelPipeline是由一系列的ChannelHandlerContext实例组成的双向链表,每个ChannelHandlerContext实例包装了一个ChannelHandler。当事件ChannelPipeline传播时,它实际上是通过ChannelHandlerContext的双向链表结构进行的。每个ChannelHandlerContext都有指向链表中前一个和后一个上下文的引用,这使得事件能够按顺序在管道中的处理器之间传递。虽然,在ChannelHandlerContext可以获取ChannelHandler,但是你怎么知道该ChannelHandler可以执行channelRead()呢?难道通过反射去判断它是否有channelRead()?虽然可行,但效率太低,Netty采取了一种简单而又高效的方式。在Netty中有一个类ChannelHandlerMask,
回答Netty保持长连接有两种方式。心跳机制心跳机制可以用于定期检测连接的可用性,防止连接由于长时间无数据传输而被网络设备(如路由器)关闭。Netty提供了IdleStateHandler,它可以检测连接是否出现读空闲、写空闲或读写空闲,如若出现空闲,则IdleStateHandler****会产生一个IdleStateEvent事件,我们可以利用这个事件来执行相应的心跳逻辑(如发送心跳数据),以维持连接的活跃状态,保持长连接。关于Netty心跳机制,请阅读这两篇文章:为什么需要心跳机制?Netty中心跳机制了解么?Netty支持哪些心跳类型设置?利用参数SO_KEEPALIVETCP协议实际上自带就有长连接选项,它本身也是心跳包机制。在TCP协议中,一旦客户端和服务端建立了连接,除非一方主动关闭或网络异常,这个连接就会一直保持开启状态。Netty可以利用TCP协议的这个特性来实现应用层的
回答在Netty中有多种方式可以发送消息。Channel.writeAndFlush():调用Channel的write()方法是最直接的方式。这种方式发送的消息会使数据经过整个出站处理器链。ChannelHandlerContext.writeAndFlush():在ChannelHandler中我们通过调用传入的ChannelHandlerContext的writeAndFlush()。这种方式发送的消息是从当前ChannelHandler开始流经出站处理器链。ChannelPipeline.writeAndFlush():通过ChannelHandlerContext的pipeline()获取到ChannelPipeline,然后调用它的writeAndFlush()。这种方式发送的消息和Channel一致,数据会经过整个出站处理器链。详解下面大明哥来演示下上面的三种方式。新建6个C
回答在Netty中,对象池技术是Netty的性能优化技术,它是通过重用对象来减少对象创建和销毁的开销,从而提高应用程序的性能和GC的频率。ByteBuf是Netty中数据的载体,Netty对它采用了对象池技术。ByteBuf分为池化和非池化两种ByteBuf。其中池化的ByteBuf可以重用内存空间,从而减少了内存分配和垃圾回收的压力。当请求一个新的ByteBuf时,Netty会首先检查池中是否有可用的ByteBuf对象,如果有,则直接返回一个现成的对象;如果没有,则创建一个新对象并在使用后将其返回池中。Netty的对象池技术主要依赖于Recycler类。Recycler在Netty中是一个轻量级的对象池,借助Recycler,Netty可以完成对对象的重复使用。当Netty中的一个对象(如ByteBuf)不再使用了,它可以被“回收”到Recycler中,而不是被垃圾回收器回收。当需要一个
回答select、poll和epoll是Linux系统下进行I/O多路复用的三种机制。它们的主要作用是允许程序监视多个文件描述符(FD),一旦某个描述符就绪(一般是读就绪或者写就绪),就能够通知应用程序进行相应的读写操作。selectselect提供了一种查询多个文件描述符的读、写、异常状态的方法。当调用select函数时,当前线程会被阻塞,直到有文件描述符就绪(至少一个监视的文件描述符就绪或者超时)。select的优点是简单易用,提供了一种简单而有效的方式来监视多个文件描述符的状态。但是随着监视的文件描述符数量的增加,select的性能会线性下降。而且每次调用时,都需要把文件描述符集合从用户空间复制到内核空间,这个复制过程在文件描述符数量较多时尤其低效,同时select能够监视的文件描述符数量受限于FD_SETSIZE,导致最大的监视数量为1024。基于这些缺点,select不适用于大规
回答在Netty中,心跳类型的设置主要依赖于IdleStateHandler的配置。IdleStateHandler支持三种类型的空闲状态检测,分别对应于不同的心跳类型:读空闲用于检测在指定的时间间隔内没有接收到任何数据。如果在配置的时间内没有读取到对端发送的数据,则认为发生了读空闲。这种情况下,可以通过发送心跳数据来检测连接是否仍然有效。写空闲用于检测在指定的时间间隔内没有发送任何数据。如果在配置的时间内没有向对端发送数据,则会触发写空闲事件。该空闲事件一般用于客户端或服务端需要定期发送信息以保持连接活跃,可以心跳数据是否真的已发送。读写空闲用于检测在指定的时间间隔内即没有读操作也没有写操作。这种心跳类型适用于需要同时考虑读和写活动来维持连接活性的场景。具体配置如下:newIdleStateHandler(readerIdleTime,writerIdleTime,allIdleTime
回答心跳机制在长连接的场景中非常重要,它主要用于保持客户端与服务端之间的连接活跃,同时也可以检测并处理死连接或半开连接。保持连接活跃:在某些网络环境下,如果某个客户端和服务端之间长时间没有数据交换,中间的网络设备会认为这个连接已经不再使用了,则会将其关闭掉,而在实际情况下,这个连接我们是需要保持的,仅仅只是这段时间没有数据交换而已。所以,通过定期发送心跳消息,可以保持连接的活跃状态,防止被中间网络设备错误地关闭。检测死连接:因为某些原因(比如应用程序奔溃、网络设备出现故障了)导致某些连接已经失效,但连接依然“存在”,这个时候双方是无法完成数据交换的。使用心跳机制,能够及时帮助我们发现这些“静默”的失效连接,确保系统能够及时发现并处理这些问题。Netty提供了一套完整的心跳和超时处理机制,主要是利用IdleStateHandler来实现的。IdleStateHandler是Netty提供的一
回答Netty是基于JavaNIO的,其内部使用了JavaNIO的Selector来实现非阻塞IO。在JavaNIO中,Selector是基于操作系统的多路复用技术实现的,在某些情况下操作系统底层的多路复用机制可能会错误地通知JVM,表明有IO事件准备就绪,而实际上并没有任何事件,导致JavaNIO会不断地轮询检查,却始终没有事件处理,从而导致"Selector空轮询"。Netty为了解决这个bug,采取了两个步骤:检测epollbug:Netty使用空轮训计数器selectCnt来记录空轮询的次数。在每次调用select()后,如果返回的就绪事件数量为0,则表示发生了一次空轮询,则计数器selectCnt+1。如果该计数器到达设定的阈值(默认512),则Netty认为确实发生了空轮训问题。解决epollbug:Netty采用重建Selector的方式来解决这个问题。当
回答在Netty中ChannelHandler是实现业务逻辑的核心位置,我们都是通过自定义的方式来实现特定的业务逻辑。Netty提供了如下几种方式来自定义ChannelHandler。实现ChannelXxxHandler接口实现ChannelInboundHandler和ChannelOutboundHandler接口来自定义我们的业务ChannelHandler。其中ChannelInboundHandler接口负责处理入站事件的接口,ChannelOutboundHandler处理出站事件的接口。实现接口的方式虽然可行,但是我们需要实现它们所有的方法,而在实际开发过程中,我们一般都只需要实现channelRead()或者其余个别方法,没有必要去实现所有方法,所以不推荐使用。继承ChannelXxxHandlerAdapter抽象类ChannelInboundHandlerAdapte
回答Bootstrap、ServerBootstrap:引导类,用于配置整个Netty程序,串联各个组件,将这些组件串连为一个可运行的完整的程序。Bootstrap用于客户端,ServerBootstrap用于服务端。Channel:通道。Netty网络通信的核心组件,它代表了一个打开的连接。当客户端与服务端建立连接后,服务端会为客户端新建一个代表它的Channel。EventLoop:事件循环。是NettyReactor线程模型的核心处理引擎,负责处理Channel的I/O操作。当服务端为某个客户端新建Channel后,为将Channel与一个EventLoop进行绑定,在这个Channel的生命周期内,它的所有事件和任务都由与之绑定的EventLoop执行。注意,EventLoop是一个单线程执行器,这样可以避免线程安全的问题。ChannelHandler:处理器,Netty处理业务的
回答Netty的线程模型是基于Reactor模式。主要由EventLoopGroup(事件循环组)、EventLoop(事件循环)和Channel(通道)构成。在这个模型中,EventLoopGroup负责管理多个EventLoop,每个EventLoop是一个单独的线程,负责处理所有注册到其上的Channel的I/O事件。三个组件定义如下:EventLoopGroup:一组EventLoop,它负责管理和维护EventLoop。在Netty中,通常有两种EventLoopGroup:一个用于接受新的连接(bossgroup),另一个用于处理已经建立的连接的数据传输(workergroup)。EventLoop:EventLoop是Netty中的核心,每个EventLoop都是一个单线程执行器,负责处理所有分配给它的Channel的事件和I/O操作。这种单线程的设计意味着,同一个Chann
回答TCP拆包/粘包问题主要是因为TCP是一个面向流的协议,它在传输数据时没有固定边界的概念。这就意味着,当应用程序使用TCP协议进行通信时,TCP层会根据需要将发送的数据拆分为若干段(拆包),或者将多个消息合并成一段(粘包),这就导致接收端在读取这些数据的时候可能无法一次性完整地解析一个完整的消息。Netty对拆包/粘包的解决方案是提供编解码器,它提供了多种解决拆包/粘包问题的编解码器:FixedLengthFrameDecoder:固定长度的解码器。过设置固定长度,每次都解码固定大小的数据包。LineBasedFrameDecoder:行分隔符解码器。使用换行符(如\n或\r\n)作为消息的分隔符来解码消息。DelimiterBasedFrameDecoder:分隔符解码器。自定义消息的分隔符来进行消息的分割。LengthFieldBasedFrameDecoder:长度字段解码器。通
回答Linux的零拷贝主要是在OS层,而Netty的零拷贝则不同,它完全是在应用层,我们可以理解为用户态层次的,Netty的零拷贝更加偏向于优化数据操作这样的概念,主要体现在下面四个方面:Netty提供了CompositeByteBuf类,可以将多个ByteBuf合并成一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝。Netty提供了slice操作,可以将一个ByteBuf切分成多个ByteBuf,这些ByteBuf共享同一个存储区域的ByteBuf,避免了内存的拷贝。Netty提供了wrap操作,可以将byte[]数组、ByteBuf、ByteBuffer等包装成一个NettyByteBuf对象,进而避免了拷贝操作。Netty提供了FileRegion,通过FileRegion可以将文件缓冲区的数据直接传输给目标Channel,这样就避免了传统方式通过循环write方式导致
回答零拷贝是一种I/O操作优化技术,旨在减少上下文切换和CPU的拷贝时间。在传统的数据传输过程中,数据从一个存储区域复制(传输)到另一个存储区域需要经历4次上下文切换和2次CPU拷贝,这大大降低了数据传输的效率。而零拷贝通过减少一些不必要的步骤来优化这一过程。目前Linux中有如下几种技术来实现:mmap:mmap允许应用程序通过创建一个文件或设备在内存中的映射,直接访问硬件内存。这样,应用程序可以直接读写这块内存区域,而无需执行系统调用或进行数据拷贝。数据可以从存储设备直接映射到用户空间内存,减少了从内核空间到用户空间的拷贝操作。sendfile:sendfile可以直接在内核中将数据从文件系统缓冲区传输到套接字缓冲区,避免了数据在用户空间和内核空间之间的多次拷贝。sendfile+SG-DMA:真正实现了零拷贝的技术。需要网卡支持SG-DMA技术,这样就可以不需要将内核缓冲区的数据拷贝
回答ChannelPipeline是Netty的核心处理链,用于实现网络事件的动态编排和有序传播。它负责组织和编排各种ChannelHandler,使他们能够有序地组织在一起,这些ChannelHandler用于处理具体的业务逻辑和数据加工逻辑。每一个Channel在创建时都会被赋予一个新的ChannelPipeline,这个ChannelPipeline包含了一系列的ChannelHandler。当该Channel发生I/O事件后,这个事件会沿着ChannelPipeline中的ChannelHandler链进行传播。传播的顺序如下:对于入站数据(如收到数据),事件会从ChannelPipeline的头部开始,依次经过每个ChannelHandler。对于出站操作(如,写数据或关闭连接),事件会从ChannelPipeline的尾部开始,向前传递给每个ChannelHandler。Cha
回答在Netty中,一个Channel代表一个连接,而EventLoop则是一个用于处理I/O操作的事件循环。当客户端连接服务端后,服务端会为它创建一个Channel,该Channel代表了与该客户端的连接。同时,也会将该客户端绑定到一个EventLoop上,在其生命周期内,该Channel所有产生的I/O事件都由与之绑定的EventLoop来处理。所以它们两者的关系是:每个Channel都会被分配到一个EventLoop与之绑定。一旦该Channel被分配给一个EventLoop后,它将在其整个生命周期内保持这个绑定关系。同一个Channel的所有I/O操作和事件都将由与之绑定的EventLoop来处理。这样可以避免多线程并发问题,无需担心同一个Channel的操作之间的同步问题。一个EventLoop可以被分配给多个Channel,因此它可以处理多个连接的I/O操作。关于Channel
回答NioEventLoopGroup默认构造函数创建的线程数为CPU核心数*2:protectedMultithreadEventLoopGroup(intnThreads,ThreadFactorythreadFactory,Object...args){super(nThreads==0?DEFAULT_EVENT_LOOP_THREADS:nThreads,threadFactory,args);}privatestaticfinalintDEFAULT_EVENT_LOOP_THREADS=Math.max(1,SystemPropertyUtil.getInt("io.netty.eventLoopThreads",NettyRuntime.availableProcessors()*2));Math.max(1,SystemPropertyUtil.get
回答EventLoop是NettyReactor线程模型的核心处理引擎,它是Netty的事件循环机制的基础,它的本质我们理解为是一个单线程执行器。它具有如下特性和作用:单线程模型:EventLoop是单线程的,一个EventLoop绑定一个线程。在其生命周期内,它将处理所有分配给它的任务,也就是说在同一时间内,只有一个线程在处理所有的事件和任务。事件循环:EventLoop以循环的方式运行,持续检查并处理新的事件。这些事件包括网络I/O事件和用户定义的任务。循环确保每个事件都得到及时处理,同时保持了任务处理的顺序性。任务调度:EventLoop不仅可以处理I/O事件,还可以调度用户自定义的任务。处理Channel:每一个Channel都会分配给一个EventLoop,在该Channel的生命周期内,该Channel的所有I/O操作都由与它绑定的EventLoop处理。EventLoopGr
回答ByteBuf是Netty数据传输的载体,是字节缓冲区,用于在I/O操作中存储字节数据。在Netty中,数据的读写都是以ByteBuf为单位进行交互的。它与JavaNIO中的ByteBuffer存在以下几个区别:读写索引ByteBuf:提供了两个独立的索引——一个用于读操作的readerIndex,另一个用于写操作writerIndex。这使得ByteBuf同时进行读写操作变得更加方便,无需在读和写模式之间切换。ByteBuffer:只有一个位置指针,用于读写操作。在进行读写操作切换时需要调用flip(),这使得ByteBuffer使用起来不如ByteBuf灵活。引用计数ByteBuf:支持引用计数,这是的ByteBuf可以更加有效地管理和回收内存。Netty可以根据引用计数来判断一个ByteBuf是否不再被使用,当引用计数为0时,它可以被自动释放,这有助于防止内存泄露。ByteBuf
回答Netty重新设计一个Channel主要是为了解决JavaNIO中Channel的局限性,并提供更加高效、灵活且功能更多的Channel。JavaNIO的Channel功能太少,不满足Netty的要求。JavaNIO的Channel和其他组件使用起来相对比较复杂,对初学者来说使用起来较为困难。而Netty提供的Channel设计的更加抽象,提供了更易用的API。JavaNIO的ServerSocketChannel和SocketChannel是一个SPI接口,具体的实现由虚拟厂商来实现,直接通过原生ServerSocketChannel和SocketChannel来实现及满足Netty的要求,其工作量不亚于重新开发一个。Netty的Channel需要符合Netty的整体架构设计,他需要和Netty的整体架构耦合在一起,比如IO模型、基于元数据描述配置化的TCP参数等等,原生的Chann
回答在Netty中,Bootstrap主要用于配置和引导启动客户端或服务端。它负责将不同的Netty的核心组件如EventLoopGroup、Channel、ChannelPipeline等组装在一起,以简化网络应用程序的配置和启动过程。通过Bootstrap,我们可以以最少的代码和配置,快速地启动一个高性能、可伸缩的网络应用程序。其主要作用有如下几个:设置EventLoopGroup线程组:提供group()设置EventLoopGroup的线程组,该线程组其实就是Reactor的线程组。设置Channel类型:提供channel()设置Channel的I/O类型。Netty提供了多种I/O类型。配置Option参数:提供option()设置Channel相关的参数。装配流水线:提供childHandler()或者handler()用来装配ChannelHandler,这些Channel
回答Reactor模式是一种高效处理并发网络事件的设计模式,它通过一组Reactor线程(事件循环组)来监听和分发网络事件(如连接、读取、写入),并结合非阻塞I/O和用户定义的事件处理器来实现。Reactor模式的核心思想是把响应IO事件和业务处理进行分离。它通过一个或者多个线程监听I/O事件,将已经准备就绪的I/O事件分发给业务线程去处理。其核心组件有三个:Reactor组件:这是Reactor模式的核心。Reactor组件负责监听和分发事件。在一个无限循环中,它等待I/O事件的到来,然后将这些就绪的I/O事件快速分发给相应的处理程序。在Netty中,这通常对应于EventLoop组件。Acceptor组件:请求连接器。Reactor组件接收到client连接事件后,会将其转发给Acceptor,Acceptor则会接受Client的连接,建立对应的Handler,并向Reactor注册
回答BIO阻塞式I/O,NIO同步非阻塞I/O,AIO异步非阻塞I/O。它们三者之间有如下几个区别。I/O模型BIO:同步且阻塞I/O模型。服务端模型为一个连接一个线程。NIO:同步非阻塞I/O模型。服务端模型为一个线程处理多个连接。AIO:异步非阻塞I/O模型。服务端模型为一个有效请求一个线程。阻塞与否BIO:阻塞,即在读写操作完成之前,线程会一直等待,直到操作完成。NIO:非阻塞,即线程在请求读写操作时会立即返回,可以进行其他任务。AIO:非阻塞且异步,当操作完成时,会被动地通知或回调线程进行后续操作。线程模型BIO:一个连接一个线程,对于每个新的客户端连接都需要创建一个新的线程。NIO:多路复用,使用单个或几个线程来管理多个连接,通过Selector监控客户端的连接和请求。AIO:异步任务完成后,系统会主动回调,使用线程池来处理这些回调,从而实现高效的并发处理。应用场景BIO:适用于
回答I/O模型决定了数据如何从输入源(如网络)传输到应用程序,或者如何从应用程序传输到输出目的地。在《UNIX网络编程》中,有5种I/O模型:阻塞I/O模型应用程序发起一个I/O操作后,将一直等待直到操作完成。在这个过程中,应用程序被阻塞,不能执行其他任务。在简单应用中易于理解和实现,但是在高并发场景下效率较低,不推荐。非阻塞I/O模型应用程序发起一个I/O操作后不会被阻塞,而是立即返回一个状态。应用程序需要不断地检查这个I/O操作是否完成(通常通过轮询实现)。需要应用程序不断轮询检查状态,增加CPU的负担。I/O复用模型利用select、poll、epoll等机制,允许单个线程同时监视多个文件描述符(FD)。当某个FD准备就绪(可读、可写、异常等),相应的操作可以进行,无需阻塞等待其他FD。目前高并发网络编程中最常用的模型,可以有效地支持高并发场景。信号驱动I/O模型应用程序告诉内核启动