原作者 https://smartan123.github.io/book/?file=001-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/001-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E9%9D%A2%E8%AF%95%E9%A2%98%E9%9B%86%E9%94%A6#%E4%B8%80%E3%80%81tomcat%E6%9C%89%E5%93%AA%E4%BA%9B%E9%85%8D%E7%BD%AE%E9%A1%B9%E5%8F%AF%E4%BB%A5%E4%BC%98%E5%8C%96%EF%BC%9F
怕丢失做cv拷贝整理
慕课网课程地址: https://coding.imooc.com/class/chapter/442.html#Anchor
请多多支持正版
一、请阐述Netty的执行流程。
1、创建ServerBootStrap实例
2、设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel
3、设置并绑定服务端的channel
4、创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:比如编解码 SSl安全认证
5、绑定并启动监听端口
6、当轮询到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler
二、Netty高性能体现在哪些方面?
1、传输:IO模型在很大程度上决定了框架的性能,相比于bio,netty建议采用异步通信模式,因为nio一个线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。正如源码中所示,使用的是NioEventLoopGroup和NioSocketChannel来提升传输效率。
2、协议:采用什么样的通信协议,对系统的性能极其重要,netty默认提供了对Google Protobuf的支持,也可以通过扩展Netty的编解码接口,用户可以实现其它的高性能序列化框架。
3、线程:netty使用了Reactor线程模型,但Reactor细分模型的不同,对性能的影响也非常大,下面介绍常用的Reactor线程模型有三种,分别如下:
Reactor单线程模型:单线程模型的线程即作为NIO服务端接收客户端的TCP连接,又作为NIO客户端向服务端发起TCP连接,即读取通信对端的请求或者应答消息,又向通信对端发送消息请求或者应答消息。理论上一个线程可以独立处理所有IO相关的操作,但一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送,又因为当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈。
Reactor多线程模型:有专门一个NIO线程用于监听服务端,接收客户端的TCP连接请求;网络IO操作(读写)由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现。但百万客户端并发连接时,一个nio线程用来监听和接受明显不够,因此有了主从多线程模型。
主从Reactor多线程模型:利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题,即把监听服务端,接收客户端的TCP连接请求分给一个线程池。因此,在代码中可以看到,我们在server端选择的就是这种方式,并且也推荐使用该线程模型。在启动类中创建不同的EventLoopGroup实例并通过适当的参数配置,就可以支持上述三种Reactor线程模型。
三、Netty的零拷贝体现在哪里,与操作系统上的有什么区别?
Zero-copy就是在操作数据时, 不需要将数据buffer从一个内存区域拷贝到另一个内存区域。 少了一次内存的拷贝,CPU的效率就得到的提升。在OS层面上的Zero-copy 通常指避免在 用户态(User-space)与内核态(Kernel-space)之间来回拷贝数据。Netty的Zero-copy完全是在用户态(Java 层面)的, 更多的偏向于优化数据操作。
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
四、原生的NIO存在Epoll bug、Netty是怎么解决的?
Java NIO Epoll会导致Selector空轮询,最终导致CPU100% 。
Netty对Selector的select 操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生 N次空轮询,则判断触发了Epoll死循环Bug 。
五、Netty自己实现的ByteBuf有什么优点?
1、它可以被用户自定义的缓冲区类型扩展
2、通过内置的符合缓冲区类型实现了透明的零拷贝
3、读和写使用了不同的索引
4、支持方法的链式调用
5、支持池化
六、Netty为什么要实现内存管理?
1、频繁分配、释放buffer时减少了GC压力。
2、在初始化新buffer时减少内存带宽消耗( 初始化时不可避免的要给buffer数组赋初始值 )。
3、及时的释放direct buffer。
七、TCP粘包/拆包的产生原因,应该这么解决?
TCP 是以流的方式来处理数据,所以会导致粘包/拆包 拆包:一个完整的包可能会被TCP拆分成多个包进行发送。 粘包:也可能把小的封装成一个大的数据包发送。
Netty中提供了多个Decoder解析类用于解决上述问题 FixedLengthFrameDecoder 、LengthFieldBasedFrameDecoder ,固定长度是消息头指定消息长度的一种形式,进行粘包拆包处理的。 LineBasedFrameDecoder 、DelimiterBasedFrameDecoder ,换行是于指定消息边界方式的一种形式,进行消息粘包拆包处理的。
八、netty业务handler中channelread方法造成内存泄漏的原因是什么?
如果业务handler继承的是ChannelInboundHandlerAdapter,那么在调用完channelRead方法之后,netty不会主动释放内存,必须进行手工释放。
九、netty并行执行优化策略有哪些?分别用在什么场景中?
1、使用netty提供的EventExecutorGroup线程组
如果客户端的并发连接数channel多,且每个客户端channel的业务请求阻塞不多,那么使用EventExecutorGroup
2、使用jdk提供的线程组ExecutorService
如果客户端并发连接数channel不多,但是客户端channel的业务请求阻塞较多(复杂业务处理和数据库处理),那么使用ExecutorService