Netty常见面试题

大家好,我是山茶!

关于我:我是山茶,一个专注于技术的菜鸟。你懂的越多,就懂得不懂的越多。关注➕我,一起了解更多知识。

前言

Netty 是 一个异步事件驱动 的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。所以在常见的面试中总是会被问到其中的一二,因此在下面总结一些常见的Netty面试题目。

1.NIO、BIO、AIO 三者间的区别是什么?

NIO(Non-blocking I/O New I/O) :同步非阻塞IO,由三大核心组件Channel(通道)、ByteBuffer(缓冲区)、Selector(选择器)组成,使用selector多路复用技术,一个线程处理多个Channel请求,多个Channel注册在selector多路复用器上,selector轮询并根据四种标识(connect、accept、read、write)进行区分处理。JDK1.4开始引入使用,数据在通道中进行读取操作,线程在发送完或读写数据时,会继续做其他事情,不会等待阻塞。且Channel是双向的,可以进行双向操作,不似BIO,要么是输入流,要么是输出流。

BIO(Blocking I/O) :同步阻塞IO,线程发起IO请求后,一直阻塞IO,直到缓冲区数据就绪后,再进入下一步操作。一个连接对应一个线程,每创建一个连接就会同步创建一个线程。JDK1.4前使用,数据流形式数据传输方式,数据流以字节、字符流形式体现,且只能为输入流或输出流,为单向流形态。

AIO( Asynchronous I/O ) :异步非阻塞IO,是JDK7后引入的NIO 2.0版本。是真正的异步操作,既数据发送完成后直接返回,后续完成既失败或成功的信息由系统回调对应的线程,通知继续进行下一步的操作。本质是让内核系统完成,用户线程只需要告诉内核,当缓冲区就绪后,通知执行我交给你的回调函数。

参考:如何理解BIO、NIO、AIO的区别? - 掘金JAVA BIO与NIO、AIO的区别(容易理解)BIO NIO AIO区别NIO编程及其三大核心原理 | 远方的灯塔 - 专注于服务端技术分享

2.Netty是什么?

  • Netty是基于NIO(Nonblocking I/O,非阻塞IO)的网络应用程序框架,它可以提供对TCP、UDP和文件传输的支持,应用场景较为广泛。
  • 分布式系统中,各个节点的调用需要远程服务调用,高性能RPC框架必不可少,Netty作为异步高性能的通信框架,通常被作为基础通信组件使用,典型的有Dubbo框架******、RocketMQ、Elasticsearch、gRPC等
  • Netty更多的被使用在项目中的统计可以从官网中查看Netty.docs: Related projects

2.Netty有哪些应用场景?

因为Netty是基于NIO实现的,所以在使用场景上,NIO能够做到的Netty也同样可以做到,且青出于蓝胜于蓝,做的会更好

  • RPC框架 基础通信组件:比如阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信
  • HTTP服务器开发:可以实现自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等
  • 即时通信系统 :使用Netty可以实现即时通信聊天的程序,在github、gitee上有很多存在,例如netty聊天室
  • 游戏通信推送:Netty本身提供了 TCP/UDP 和 HTTP 协议栈,便于定制和开发私有协议栈。游戏用户登录、用户聊天等都可以方便的通过 Netty 进行高性能通信

还有很多的其他使用场景,但在工作汇中最常见的场景就是作为一个RPC框架的通信组件以及游戏中的实时通信推送场景。

3.Netty的优势是什么?为什么使用Netty?

  • API 使用简单,开发门槛相对较低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 可扩展性强,可以通过 ChannelHandler 对通信框架进行灵活的扩展;
  • 高性能,Netty采用异步、事件驱动的方式处理网络请求。这种处理方式相比传统的同步阻塞I/O模型,能够更好地利用CPU资源,提高系统的吞吐量和响应速度;
  • 社区活跃,Netty是一个开源项目,拥有活跃的社区和广泛的用户群体,因此它在功能上得到了不断的更新和改进,以能够满足不断变化的需求;
  • 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它完全满足不同行业的商用标准

综述,Netty能够提高应用程序的性能、可扩展性和可靠性,同时也能提高开发效率和代码质量。因此,Netty被广泛应用于构建高性能的网络应用程序,例如游戏服务器、聊天应用程序、实时数据传输等

4.BIO、NIO、AIO的区别

  • BIO(Blocking IO,一个连接一个线程) :同步阻塞IO,每一个读或者写操作均会启动一个线程进行处理,并且等待上一操作完成才会进行下一个操作。当在少量数据处理丽连接时,不会出现问题,但要处理的数据达到十万级、百万级、甚至千万级连接时,BIO会增加整体的性能功耗,这时BIO无法满足我们高并发的需求,我们需要更好的一种IO模式来处理。
  • NIO(Non Blocking IO或New IO,一个请求一个线程) :同步非阻塞IO,在Java1.4开始支持并使用,对应使用的包是java.nio。NIO使用通道和缓冲区。数据总是从缓冲区写入通道,并从通道读取到缓冲区,且使用selector选择器进行轮询监听。对于高并发、高负载的情况,使用NIO的非阻塞模式进行开发。
  • AIO(Asynchronous IO一个有效请求一个线程) :异步非阻塞IO,又称为NIO2.0,从JDK7开始支持,AIO的使用是基于事件的回调机制实现的,既一个事件发出请求,不会在该位置等待结果阻塞,会直接返回,当后台处理完成后,操作系统会通知对应的线程进行接下来的操作。AIO的使用目前暂时没有NIO的使用广泛,曾经Netty也是用AIO,后续则是切换为NIO

5 . Netty为什么选择NIO?不选择AIO

想知道这个的原因首先需要了解什么事NIO、什么是AIO,他们的底层使用是什么样的,底层调用的逻辑是什么样的。

  • NIO模型:同步非阻塞型IO, NIO有同步阻塞和同步非阻塞两种模式,一般讲的是同步非阻塞,服务器实现模式为一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
  • AIO模型:异步非阻塞型IO,( AIO又称NIO2.0,JDK7才开始支持)服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

至于没有选择AIO的原因,作者原文

Not faster than NIO (epoll) on unix systems (which is true) :在unix上并没有比NIO快

There is no daragram suppport :不支持数据报

Unnecessary threading model (too much abstraction without usage):太多没有用的抽象

主要有三点:

    • Netty不注重windows的使用,而在linux上的使用,AIO在windows实现成熟,但windows很少用作服务器
    • Linux下AIO相比较NIO性能提升不明显
    • AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存, 所以对连接数量非常大但流量小的情况, 内存会存在严重浪费的情况

综述,Netty没有选择AIO,而是选择了NIO。

6.Netty有哪些核心组件?

  • Bootstrap、ServerBootstrap(Netty启动类)

Bootstrap是客户端的引导类,ServerBootstrap是服务端引导类

  • Channel:Netty网络通信的组件,用于网络IO操作;Channel提供异步的网络IO操作,调用后立即返回ChannelFuture,通过注册监听,或者同步等待,最终获取结果;
  • ChannelHandler:属于业务的核心接口,用于处理IO事件或者拦截IO操作,并将其转发到ChannelPipeline(业务处理链)中的下一个处理程
  • ChannelPipeline:是一个handler的集合,它负责处理和拦截出站和入站的事件和操作;在Netty中,每个Channel都有且只有一个ChannelPipeline与之对
  • ChannelHandlerContext:保存Channel相关的所有上下文信息,同时关联一个ChannelHandler。
  • NioEventLoopGroupNioEventLoop:NioEventLoopGroup可以理解为线程池,NioEventLoop理解为一个线程,每个EventLoop对应一个Selector,负责处理多个Channel上的事件。

7.Netty的高性能表现在哪些地方?

  • 异步非阻塞的 IO 模型:Netty 使用了 Java NIO 框架,通过 Selector 实现了异步非阻塞 IO,避免了传统 IO 模型中的阻塞等待,从而大大提高了网络通信的效率和吞吐量。
  • 零拷贝优化:Netty 通过使用 Direct Memory Buffer 技术,避免了传统 IO 模型中的数据拷贝过程,减少了 CPU 和内存的开销,提高了网络通信的效率。
  • 线程模型优化:Netty 的线程模型采用了多线程 Reactor 模型,通过使用多个 Reactor 线程处理网络 IO 事件,避免了传统 IO 模型中的阻塞等待,提高了网络通信的吞吐量。
  • 可扩展性优化:Netty 提供了高度可扩展的设计,通过 ChannelPipeline、ChannelHandler 等组件,可以方便地扩展和定制网络通信的行为和处理逻辑。

8.什么是零拷贝?Netty是怎么实现零拷贝的?

  • 零拷贝(Zero-Copy)是指在数据传输过程中,避免将数据从一个内存区域拷贝到另一个内存区域的过程,从而减少了CPU和内存的开销,提高了数据传输的效率。
  • 在传统的IO模型中,数据从磁盘或网络中读取到内核缓冲区,然后再被拷贝到应用程序的用户缓冲区,最后再被拷贝到网络或磁盘中。这种数据拷贝的过程会产生多次内存复制和上下文切换,导致CPU和内存的开销增加,降低了数据传输的效率。而零拷贝技术则可以避免这些不必要的内存复制和上下文切换,提高了数据传输的效率。

Netty实现零拷贝

  • 使用 Direct Memory Buffer:Netty 通过使用 Direct Memory Buffer 来实现零拷贝。Direct Memory Buffer 是一种在堆外内存中分配的缓冲区,可以直接访问内存,而不需要将数据从内核缓冲区复制到用户缓冲区。因此,使用 Direct Memory Buffer 可以避免内存复制的过程,提高数据传输的效率。
  • 使用 FileRegion:Netty 还提供了 FileRegion 类,可以将磁盘上的文件映射到内存中,从而避免了数据从磁盘到内核缓冲区的复制过程。这种方式同样可以实现零拷贝。

9.Netty中线程模型是什么样的?

Netty中的线程模型是属于Reactor模型的,包括三种实现方式Reactor单线程模型、Reactor多线程模型、Reactor主从多线程模型,目前使用的方式是Reactor主从多线程模型,很好的解决了高并发的性能问题。如果给Reactor线程模式下一个定义,则可以理解为:Reactor线程模式 = Reactor(I/O多路复用)+ 线程,Reactor负责监听和分配事件,线程池负责处理事件。

Reactor单线程模型

Reactor内部通过 selector 监听连接事件,收到事件后通过dispatch进行分发。

  • 如果是连接建立的事件,通过accept接受连接,并创建一个Handler来处理连接后续的各种事件。
  • 如果是读写事件,直接调用连接对应的Handler来处理,Handler完成 read => (decode => compute => encode) => send 的全部流程

从始至终,整个过程都只有一个线程在执行操作。

不过改种模式下,在请求过多的时候,只有一个线程处理,无法发挥cpu多核处理性能,且一旦其中一个Handler阻塞,服务端整体则无法继续处理连接事物

Reactor多线程模型

为了提高性能,我们可以把复杂的事件处理handler交给线程池,那就可以演进为 「Reactor多线程模型」 。

通过select监听客户请求,如果是连接建立的事件,通过accept接受连接,并创建一个Handler来处理连接后续的读写事件。这里的Handler只负责响应事件、read和write事件,会将具体的业务处理交由Worker线程池处理。处理所有业务事件,包括(decode => compute => encode) 过程。能充分利用多核机器的资源,提高性能。

但是,在高并发情况下,一个Reactor线程负责监听和处理所有的客户端连接可能会存在性能问题。

Reactor主从多线程模型

为了充分利用多核能力,可以构建两个 Reactor,这就演进为 「Reactor主从多线程模型」 。

比起第二种模型,它是将Reactor分成两部分:

  1. mainReactor负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给subReactor。
  2. subReactor主要做和建立起来的socket做数据交互和事件业务处理操作。通常,subReactor个数上可与CPU个数等同。

Nginx、Swoole、Memcached和Netty都是采用这种实现。

Reactor模型优点:

  • 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
  • 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销
  • 可扩展性,可以方便地通过增加Reactor实例个数来充分利用CPU资源
  • 可复用性,Reactor模型本身与具体事件处理逻辑无关,具有很高的复用性。

10.什么是TCP粘包和半包?

TCP是个"流"协议,所谓流,就是没有界限的一串数据。客户端发送数据时,实际时把数据写进了Tcp发送的缓冲区里面。

  • 半包:收到半个包。如送的包的大小比TCP发送缓冲区容量大或者发送的数据大于协议的最大传输单元(MTU),那么这个数据包就会被分成多个包,通过socket多次发送到服务端,服务端第一次从缓冲区获取到的数据时整个全包的一部分
  • 粘包:发送的包的大小比TCP发送缓冲区容量小,并且TCP缓冲区可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从接受缓存就可能一下读取了多个包

举例说明:比如发送2条数据:ABC、DEF ,服务端可能一次就收到ABCDEF(粘包现象) ,也可能收到AB、CD、DE(半包现象)

11.Netty如和解决粘包和拆包?

解决原理:找出消息的边界

NIO解决方式:

1)固定设置一个缓冲区空间大小,比如1024,但是存在一个很明显的问题,容易造成空间浪费

2)设置一个固定的分割符,设定缓存空间固定,根据分割符多次读取,比如设定分割符为"\n"

3)传送前先发送一个大小的信息给服务端,服务端创建对应大小缓冲区(HTTP的实现方式)

Netty的解决方式:

对三种常用封装成帧进行支持 (以下类都继承ByteToMessageDecoder,ByteToMessageDecoder又继承ChannelInboundHandlerAdapter)

消息固定长度 FixedLengthFrameDecoder
自定义分隔符分割 DelimiterBasedFrameDecoder
换行符进行分割 LineBasedFrameDecoder
消息分为消息头和消息体,消息头中包含表示消息总长度 LengthFieldBasedFrameDecoder
相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟6 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity6 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天7 小时前
java的threadlocal为何内存泄漏
java
caridle7 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^7 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋37 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx