文章目录
- 快速记忆
- IO模型相关
- 零拷贝
- Netty的基本组件
- Netty的使用
-
-
- Netty 应用场景了解么?
- 那些开源项目用到了 Netty?
- Netty支持哪些协议?
- 什么是编解码器(Codec)?Netty中有哪些内置的编解码器?
- 请解释Netty的事件模型是什么样的。
- Netty的线程模型是什么?为什么它是高效的?
- Netty 中的线程模型有哪几种?
- 如何优化Netty应用的性能?
- 请分享一下你在项目中使用Netty的经验。
- Netty如何处理安全性问题?
- Netty如何处理粘包和拆包问题?
- 有没有使用Netty实现过高性能的网络应用?
- Netty 长连接、心跳机制了解么?
- Netty如何支持WebSocket?
- Netty对HTTP/2的支持是怎样的?
- 如何将长链接转换成短链接,并发送短信?
- 长链接和短链接如何互相转换?
- 长链接和短链接的对应关系如何存储?
-
快速记忆
同步与异步
并发与并行:并发就是交替执行,并行就是真正的并行
同步与异步:从字面意思上就可以理解,异步里面有个异字,这个异字代表别人,所以同步与异步区分一个现场还是两个线程;
阻塞与非阻塞:强调等待还是不等待
5种阻塞模型:同步阻塞I/O、同步非阻塞I/O、同步多路复用、异步阻塞(没有此情况)、异步非阻塞I/O、信号驱动 I/O
netty的优点
《netty的关键字:Ractors
+Selector
+ByteBuf
+线程池
+编码解码器
》:
- 事件驱动(Reactors): Netty采用了
Reactors设计模式
,网络操作(如连接、读取数据、写入数据等)都被视为事件。Netty的事件驱动模型通过注册事件监听器,当特定事件发生时,会触发相应的回调方法。这种模型避免了线程阻塞,提高了并发性能。 - NIO+Selector多路复用: Netty使用了非阻塞的IO模型,通过Selector实现多路复用,使得一个线程可以处理多个Channel的IO事件。
- 池化和复用: 池化的最大意义在于可以重用 ByteBuf,以此减少内存分配和垃圾回收的开销,提高了系统的性能。
- 自带编解码器解决 TCP 粘包/拆包问题:ByteToMessageDecoder、MessageToByteEncoder、StringDecoder和StringEncoder、ObjectDecoder和ObjectEncoder、LengthFieldBasedFrameDecoder
- Netty支持多种常见的网络协议,包括TCP、UDP、HTTP、WebSocket等。这使得它适用于各种应用场景
Netty的核心组件是什么?
巧计:一个EventLoop
绑定了多个Channel
,一个Channel
会关联一个ChannelPipeline
,一个ChannelPipeline
里面会有很多的ChannelHandler
Netty的核心组件包括以下几个主要部分:
- Channel(通道):
- Channel代表了一个网络连接,可以是客户端到服务器的连接,也可以是服务器之间的连接。每个Channel都由一个EventLoop负责处理,而一个EventLoop可以管理多个Channel。
- Channel是处理数据的通道。Channel负责所有的I/O操作(读取、写入、连接和绑定等)
- EventLoop(事件循环): 每个Channel都与一个EventLoop关联,它负责处理分配给它的所有Channel(通道)的所有事件。
EventLoop
会不断地轮询注册在其上的Channel
,处理这些Channel
上的IO事件,这样可以确保Channel的所有事件都在同一个线程中按顺序处理,从而避免了多线程并发带来的复杂性。一个EventLoop可能处理着多个channel的事件。一个EventLoop通常关联一个线程,而一个Netty应用可能有多个EventLoop,每个EventLoop运行在独立的线程上 - ChannelPipeline(通道管道):
ChannelPipeline
是一个由一系列ChannelHandler
组成的处理链。ChannelHandler
如进行数据编解码、业务逻辑处理等。ChannelPipeline
将这些ChannelHandler
串联起来,形成一个处理链。- 每个
Channel
都有一个关联的ChannelPipeline
,用于处理入站和出站的数据。
- ChannelHandler(通道处理器): 在Netty中,网络操作(如连接、读取数据、写入数据等)都被视为事件。当这些事件发生时,Netty会将这些事件传递给相应的事件处理器(Handler)。事件处理器就像是处理网络事件的“工人”,它们负责实现数据编解码、业务处理等。ChannelHandler可添加到ChannelPipeline中,形成一个处理链。
- ByteBuf(字节缓冲区): NIO里面有ByteBuffer,而Netty里面用ByteBuf,ByteBuf是对ByteBuffer的增强。ByteBuf是Netty中用于存储字节数据的缓冲区。它提供了灵活的API,支持池化、切片等高级特性,有助于提高性能和内存管理效率。
- ChannelFuture(通道未来): Netty 中所有的 I/O 操作都为异步的,我们不能立刻得到操作是否执行成功。不过,我们可以通过 ChannelFuture 接口的 addListener() 方法注册一个ChannelFutureListener ,当操作执行成功或者失败时,监听就会自动触发返回结果。
- Bootstrap(引导类): Bootstrap是Netty用于启动客户端的引导类。它负责配置和启动Netty的客户端组件,包括设置Channel类型、EventLoop组、ChannelPipeline等。
- ServerBootstrap(服务器引导类): ServerBootstrap是Netty用于启动服务器的引导类。它包含了一些服务器独有的配置选项,如处理客户端连接请求、设置TCP参数等。
ByteBuf与ByteBuffer区别
Netty里面有ByteBuf,而NIO里面是ByteBuffer。ByteBuf是对ByteBuffer的增强,是对字节数据的封装。
①ByteBuf是可以动态扩容的,而ByteBuffer一旦指定了容量就固定了,所以ByteBuf是对ByteBuffer的增强
②bytebuffer是读写共用一个指针,所以需要读写的切换;现在bytebuf就不用
③ByteBuf的设计允许进行零拷贝操作
Netty 服务端和客户端的启动过程了解么?
Netty服务端和客户端的启动过程主要涉及几个关键步骤,包括服务端的启动、客户端的启动,以及两者之间的连接建立过程。
服务端启动过程:
【ServerBootstrap】
【Boss NioEventLoopGroup - NioServerSocketChannel】
【Worker NioEventLoopGroup - NioServerSocketChannel】
- 服务端首先通过ServerBootstrap类进行启动,这是Netty用于启动服务器的引导类。
- 绑定NioEventLoopGroup,让它负责处理所有的Channel上面的事件
- 创建NioServerSocketChannel,用于监听和接受客户端的连接请求。
- 当有客户端连接请求时,服务端会创建一个新的NioSocketChannel,由Worker NioEventLoopGroup处理网络的读写事件。
客户端启动过程:
【Bootstrap】
【NioEventLoopGroup - NioSocketChannel】
- 客户端通过Bootstrap类进行启动,这是Netty用于启动客户端的引导类。
- 客户端绑定NioEventLoopGroup,但与服务端不同的是,客户端的EventLoopGroup主要用于处理异步操作和定时任务。
- 客户端通过NioSocketChannel连接到服务端,这是一个双向的通信通道,用于发送和接收数据。
连接建立过程:
- 服务端通过
Boss NioEventLoopGroup
接收客户端的连接请求,一旦连接建立,就将NioSocketChannel注册到Worker NioEventLoopGroup
。 - 客户端在连接建立过程中,会调用sync()方法阻塞,直到连接成功建立。连接建立后,客户端和服务端都会调用initChannel方法,可以通过ChannelHandler处理网络事件和数据传输。
- writeAndFlush代表发送数据,比如发送“hello”,会发送到NioEventLoopGroup,处理读事件,然后交给handler依次处理
如何优化Netty应用的性能?
《netty的关键字:Ractors
+Selector
+ByteBuf
+线程池
+编码解码器
》:
从参数
、ByteBuf
、线程池
、编码解码器
等角度来说
优化Netty应用的性能涉及多个方面,以下是一些常见的优化策略:
- 合理使用ChannelOption: Netty提供了一些ChannelOption,用于配置底层的Socket选项。合理配置这些选项可以优化TCP连接的性能,例如设置TCP_NODELAY、SO_KEEPALIVE等。
// 启用TCP_NODELAY,禁用Nagle算法
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 启用TCP_KEEPALIVE,保持长连接
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
tcp_nodelay 参数:
TCP 协议的包头有 20 字节,IP 协议的包头也有 20 字节,如果仅仅传输1 字节的数据在网络上传输的就有 20 + 20 + 1 = 41 字节,其中真正有用的数据只有1个字节,这对效率和带宽是极大的浪费。所以有个Nagle算法:如果是连续的小数据包,大小没有一个 MSS(Maximum SegmentSize,最大分段大小),那么这些小数据包就会在发送端暂存起来,直到小数据包累积到一个 MSS,或者收到一个Ack 为止,这种算法会造成网络延迟。
所以需要在 Socket 上开启 tcp_nodelay ,这个参数关闭了Nagle`s 算法,这样发送端就不需要等到上一个发送包的 ACK 返回直接发送新的数据包就好了。这对于强网络交互的场景来说非常的适用。
- 合理使用编解码器: Netty提供了一些内置的编解码器,同时也支持自定义编解码器。合理选择和配置编解码器可以减少数据的序列化和反序列化开销。
- 内置编解码器:Netty提供了许多内置的编解码器,如StringDecoder和StringEncoder用于处理字符串,ByteToMessageDecoder和MessageToByteEncoder用于处理自定义的消息类型。通常,你会继承ByteToMessageDecoder或MessageToByteEncoder来实现自定义的编解码逻辑。
- 第三方库:如果需要处理特定的协议(如HTTP、Protobuf、JSON等),可以使用第三方库提供的编解码器。
- 如果对于性能要求不高,在传输数据占用带宽不大的场景下可以使用JSON作为序列化协议;
- 如果对于性能要求比较高,那么使用 Thrift 或者 Protobuf 都可以。而Thrift 提供了配套的 RPC 框架,所以想要一体化的解决方案,你可以优先考虑Thrift;
- 线程:
- EventLoopGroup:合理配置EventLoopGroup的线程数,对于高并发应用,可以增加EventLoop的数量
- 避免阻塞操作:Netty的设计是基于非阻塞IO的,阻塞操作会影响整体性能。
- 使用池化的ByteBuf: Netty的ByteBuf提供了内存池化的机制,通过重用ByteBuf对象,可以减少内存分配和垃圾回收的开销,提高性能。可以使用
PooledByteBufAllocator
来启用ByteBuf的池化。
// 启用ByteBuf的池化
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
- 合理使用内存分配器: Netty支持多种内存分配器,包括堆内存和直接内存。根据应用的特性选择合适的内存分配器,例如使用
UnpooledHeapByteBufAllocator
和UnpooledDirectByteBufAllocator
。 - 合理处理异常和资源释放: 在Netty中,异常的处理和资源的释放非常重要。及时处理异常,释放资源可以避免内存泄漏和性能下降。
- 优雅关闭: Netty支持优雅关闭,确保在关闭服务器或连接时不会丢失任何正在处理的数据。通过适当地使用
ChannelGroup
或其他机制,可以实现连接的安全关闭。
Netty中有哪些内置的编解码器?
ByteToMessageDecoder、MessageToByteEncoder、StringDecoder和StringEncoder、ObjectDecoder和ObjectEncoder、LengthFieldBasedFrameDecoder: 根据长度字段来拆分消息,用于处理消息长度不固定的情况。
IO模型相关
同步与异步
同步与异步:从字面意思上就可以理解,异步里面有个异字,这个异字代表别人,所以同步与异步区分一个现场还是两个线程;
阻塞与非阻塞:强调等待还是不等待
同步:有一个活分为好多步骤,一个线程自己从头到尾自己把活干完(同步只有一个线程)
异步:有一个活分为好多步骤,至少有两个线程的情况下,A线程干活的情况下B线程也在干活,一起把这个活干完(异步至少有两个线程)
阻塞与非阻塞
同步与异步:从字面意思上就可以理解,异步里面有个异字,这个异字代表别人,所以同步与异步区分一个现场还是两个线程;
阻塞与非阻塞:强调等待还是不等待
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.,把自己“挂起”等待结果的叫阻塞,还可以干别的活叫非阻塞
阻塞就是 线程执行A任务时,A任务有个停顿等待的过程,这个线程执行A时只能跟着阻塞等待不能干别的活
非阻塞就是 线程执行A任务时,A任务有个停顿等待的过程,这个线程执行A时不阻塞等待,它能干别的活
阻塞:假设你是一个餐厅的服务员,你正在等厨师做好一道菜给顾客。在厨师做菜的过程中,你什么都做不了,只能干等着,直到菜做好了你才能继续服务下一位顾客。这就是阻塞IO,你的工作(服务员的工作)被阻塞在了等待厨师做菜(IO操作)上
非阻塞:假设你是一个餐厅的服务员,你告诉厨师顾客要什么菜,然后你就去忙其他事情了(比如接待其他顾客)。每隔一段时间,你就去问一下厨师你的菜好了没有。这种方式虽然不会让你一直等着,但你需要不断地去询问,也就是“轮询”,这会消耗你的时间和精力(CPU资源)。
多路复用
多路复用:有一个selector专门负责监视多个IO操作然后及时反馈给当前线程,以便于当前线程可以专心搞其它事情(多路复用本质也是当前这一个线程在干活,所以它也是同步的,所以叫同步多路复用)
现在有一个多路复用器来帮你管理多个订餐请求。你告诉多路复用器你想要哪些餐厅的哪些菜,然后你就去忙其他事情了。多路复用器会帮你盯着这些餐厅,一旦有餐好了,他就会通知你来取。这样,你既可以处理多个订餐请求,又不需要一直等着或不断地去询问,大大提高了效率。
IO 模型
5种阻塞模型:同步阻塞I/O、同步非阻塞I/O、同步多路复用、异步阻塞(没有此情况)、异步非阻塞I/O、信号驱动 I/O
同步阻塞IO:是一个线程在处理IO请求,它处理这个客户端的IO的时候哪怕这个客户端的IO闲着它也不能处理别的客户端的IO,一个线程在干活所以是同步的,干活的时候不能干别的所以叫阻塞,所以它是同步阻塞IO
同步非阻塞IO:是一个线程在处理IO请求,它处理这个客户端的IO的时候如果这个客户端的IO闲着它可以处理别的客户端的IO,一个线程在干活所以是同步的,干活的时候可以干别的所以叫非阻塞,所以它是同步非阻塞IO
多路复用:有一个selector专门负责监视多个IO操作然后及时反馈给当前线程,以便于当前线程可以专心搞其它事情(多路复用本质也是当前这一个线程在干活,所以它也是同步的,所以叫同步多路复用)
异步阻塞(没有此情况)
异步非阻塞:有两个线程在干活,让另一个线程干活然后把结果告诉你
BIO、NIO、AIO
BIO:属于同步阻塞 IO 模型 。数据的读取写入必须阻塞在一个线程内等待其完成。
NIO:属于同步非阻塞的 I/O模型。网络通信基本都是NIO
AIO:(Asynchronous I/O)异步的IO模型。应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。
缓冲区是什么意思?
缓冲区实质上是一个数组。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。
种类:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
Java有哪些数据类型
基本数据类型sout打印出来的是数值, 引用数据类型sout打印出来的是对象的地址值
其中八种基本数据类型:byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double,boolean
所占的空间大小:
byte(1)
short(2)、char(2)
int(4)、long(8)
float(4)、double(8)
说说这 8 种基本数据类型对应的包装类型?
这八种基本类型都有对应的包装类分别为:Byte、Short、Character、Integer、Long、Float、Double、Boolean
那基本类型和包装类型有啥区别不?
Byte、Short、Character、Integer、Long、Float、Double、Boolean这些包装类的默认值是null,因为它们是对象类型。
基本类型是有默认值的:byte默认值0、short默认值0、char默认值’\u0000’、int默认值0、long默认值0L、float默认值0.0f、double默认值0.0d,boolean默认值false
零拷贝
零拷贝
传统的 IO 将一个文件通过 socket 写出,内部工作流程是这样的:
在传统的数据传输过程中,当应用程序需要从磁盘读取数据并通过网络发送时,通常需要经历以下步骤:
这个过程涉及了多次数据拷贝和上下文切换(用户态和内核态之间的切换),这会导致CPU资源的浪费和数据传输效率的降低。
“零拷贝”这个名字听起来很神奇,好像数据在传输过程中完全没有被拷贝一样。但实际上,这里的“零拷贝”是指减少了数据在用户空间和内核空间之间的拷贝次数,并不是没有拷贝而是减少了拷贝次数,特别是避免了将数据从内核空间拷贝到用户空间,然后再从用户空间拷贝回内核空间(如网络发送缓冲区)的过程。这样,数据的传输变得更加高效,减少了CPU的负担和内存带宽的占用。
Netty 的零拷贝了解么?
在 OS 层面上的 零拷贝通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据。而在 Netty 层面 ,零拷贝主要体现在对于数据操作的优化。
Netty 中的零拷贝体现在以下几个方面
- 使用 Netty 提供的 CompositeByteBuf 类, 可以将多个ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝。
- ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝。
- ByteBuf的duplicate操作