JAVA netty,netty工作原理
如何解决写爬虫IP受阻的问题?立即使用。
一、 Netty简介
Netty是一个高性能、异步事件驱动的NIO框架,基于JAVA NIO提供的API。它支持TCP、UDP和文件传输。作为一个异步NIO框架,Netty的所有IO操作都是异步的、非阻塞的。通过Future-Listener机制,用户可以方便、主动地获取IO操作结果,或者通过通知机制获取IO操作结果。Netty作为目前最流行的NIO框架,已经广泛应用于互联网、大数据分布式计算、游戏行业、通信行业等。一些业界知名的开源组件也是建立在Netty的NIO框架之上。
二、Netty线程模型
在JAVA NIO中,选择器为反应器模式提供了基础,Netty将选择器和反应器模式结合起来设计了一个高效的线程模型。让我们先来看看反应器模式:
2.1反应器模式
维基百科对反应器模型解释如下:“反应器设计模式是一种事件处理模式,用于处理由一个或多个InP并发交付的服务请求。uts。然后,服务处理程序对传入的请求进行解复用,并将它们同步分派给相关的请求处理程序。首先,反应器模式是事件驱动的,有一个或多个并发输入源,一个服务器处理程序和多个请求处理程序。这个服务处理程序将同步地将输入请求多路复用并分发到相应的请求处理程序。如下图所示:
结构上有点类似于生产者和消费者模型,即一个或多个生产者将事件放入一个队列,一个或多个消费者主动处理来自这个队列的轮询事件;然而,在反应器模式中,没有缓冲队列。每当有事件输入到服务处理程序,服务处理程序就会根据不同的事件类型,主动将事件分发到相应的请求处理程序进行处理。
2.2创作者模式的实现
关于Java NIO对creator模式的构建,Doug lea在《Scalable IO in Java》中给出了很好的解释。这里截取PPT来说明creator模式的实现。
1.第一个实现模型如下:
这是最简单的反应堆单线程模型。由于反应器模式使用异步非阻塞IO,所有IO操作都不会被阻塞。理论上,一个线程可以独立处理所有IO操作。此时,反应器线程是一个多面手,负责解复用套接字,接受新的连接,并将请求分发到处理链。
对于一些小容量的应用场景,可以使用单线程模型。但是,对于高负载,大并发的应用并不合适,主要是因为以下原因:
(1)当一个NIO线程同时处理数百个链接时,其性能无法得到支持。NIO线程即使CPU负载达到100%,也无法完全处理消息。
(NIO线程过载时,处理速度会变慢,会造成大量客户端连接超时。超时后,往往会再次发送,这将进一步增加NIO线程的负载。
(3)可靠性低,一个线程的意外死循环会导致整个通信系统不可用。
为了解决这些问题,Reactor多线程模型出现了。
2.反应器多线程模型:
与之前的模型相比,该模型在处理链中采用了多线程(线程池)。
在大多数情况下,该模型可以满足性能要求。但是在一些特殊的应用场景下,比如服务器会对客户端的握手消息进行认证。在这种情况下,单个接受者线程可能性能不足。为了解决这些问题,产生了第三种反应器线程模型。
相关:《java开发教程》
3.反应堆主从模型
与第二种模型相比,该模型将反应器分为两部分,主反应器负责监控服务器套接字新连接并接受;并将建立的套接字分配给子反应器。SubReactor负责对连接的socket进行解复用,读写网络数据,将业务处理功能扔给worker线程池。通常,子反应器的数量可以等于CPU的数量。
2.3网状模型
2.2中描述了电抗器的三种型号,那么Netty是哪一种呢?其实Netty的线程模型是Reactor模型的变种,也就是去掉线程池的第三种形式的变种,这也是Netty NIO的默认模式。Netty中反应器模式的参与者主要有以下组件:
(1)选择器
(2)事件循环组/事件循环
(3)渠道管道
选择器是NIO中提供的SelectableChannel复用器,起到解复用器的作用,这里不再赘述。下面介绍Netty的反应器模式中的另外两个功能及其作用。
三、EventLoopGroup/EventLoop
当系统运行时,频繁的线程上下文切换会带来额外的性能损失。当一个业务流程由多个线程并发执行时,业务开发人员应该时刻警惕线程安全。哪些数据可能被并发修改,如何保护这些数据?这不仅降低了开发效率,还带来了额外的性能损失。
为了解决上述问题,Netty采用了序列化的设计理念,IO线程EventLoop始终负责消息的读取、编码和后续的处理程序执行,让人意想不到的是整个过程不会切换线程上下文,数据也不会面临并发修改的风险。这也解释了为什么Netty线程模型去掉了Reactor主从模型中的线程池。
EventLoopGroup是一组eventloops的抽象。EventLoopGroup提供了next接口,你可以按照一定的规则获取一组eventloops中的一个eventloops来处理任务。这里需要了解的是,在Netty中,在Netty服务器编程中,我们需要两个EventLoopGroup,BossEventLoopGroup和WorkerEventLoopGroup来工作。通常一个服务端口,即ServerSocketChannel,对应一个选择器和一个EventLoop线程,也就是说BossEventLoopGroup的线程数参数为1。BossEventLoop负责接收客户端的连接,并将SocketChannel交给WorkerEventLoopGroup进行IO处理。
EventLoop的实现充当反应器模式中的调度程序。
四、ChannelPipeline
事实上,ChannelPipeline在反应器模式中扮演请求处理器的角色。
ChannelPipeline的默认实现是DefaultChannelPipeline。DefaultChannelPipeline本身维护一个tail和head的ChannelHandler,用户是看不到的。它们分别位于链表队列的头部和尾部。Tail在上层,head在网络层附近。Netty中关于ChannelHandler有两个重要的接口,ChannelInBoundHandler和ChannelOutBoundHandler。入站可以理解为网络数据从系统外部流向系统内部,出站可以理解为网络数据从系统内部流向系统外部。用户实现的ChannelHandler可以根据需要实现一个或多个接口,并放入管道中的链表队列中。ChannelPipeline会根据不同的IO事件类型找到相应的处理程序进行处理,链表队列是责任链模式的一种变体。所有符合自顶向下或自底向上事件相关性的处理程序都将处理事件。
ChannelInBoundHandler处理客户端发送到服务器的消息,一般用于执行半包/粘包、解码、数据读取、业务处理等。ChannelOutBoundHandler处理从服务器发送到客户端的消息,通常用于对消息进行编码并将其发送到客户端。
下图说明了ChannelPipeline的执行过程:
有关管道的更多知识,请参考:谈论管道模型。
五、Buffer
与NIO相比,Netty提供的扩展缓冲区有很多优点。作为数据访问中非常重要的一部分,我们来看看Netty中缓冲区的特点。
1.ByteBuf读写指针
在ByteBuffer中,读写指针是position,而在byte buffe中,读写指针分别是readerIndex和writerIndex。看起来ByteBuffer只用一个指针实现了两个指针的功能,节省了变量。但是当ByteBuffer的读写状态切换时,必须调用flip方法,在下一次写入之前,必须读取Buff中的内容,然后调用clear方法。每次都是先翻页再阅读,先清空再写,这无疑给开发带来了繁琐的步骤,内容写完了才能写,非常不灵活。作为对比,我们来看看ByteBuf。读的时候只依赖readerIndex指针,写的时候只依赖writerIndex指针。我们每次读写之前都不需要调用相应的方法,也没有限制一定要一次读完。
2.零拷贝
(1)Netty使用直接缓冲区收发ByteBuffers,使用堆外直接内存读写Socket,不需要对字节缓冲区进行二次复制。如果传统的堆缓冲区用于Socket读写,JVM会将堆缓冲区的副本复制到直接内存中,然后写入Socket。与堆外的直接内存相比,发送消息时多了一个缓冲区的内存副本。
(2)Netty提供了一个组合的Buffer对象,可以聚合多个ByteBuffer对象。用户可以像操作一个缓冲区一样方便地操作组合缓冲区,避免了传统的通过内存拷贝将几个小缓冲区组合成一个大缓冲区的方式。
(3)Netty的文件传输采用transferTo方法,可以直接将文件缓冲区的数据发送到目标通道,避免了传统循环写入方式带来的内存复制问题。
3.引用计数和池技术。
在Netty中,每个应用的缓冲区对Netty来说都可能是一个宝贵的资源,所以为了获得对内存的应用和回收的更多控制,Netty通过引用计数的方法来管理内存。Netty对Buffer的使用基于DirectBuffer,大大提高了I/O操作的效率。但是相比于HeapBuffer,DirectBuffer除了I/O操作效率高之外,还有一个先天的劣势,就是对于DirectBuffer的应用效率不如HeapBuffer。因此,Netty结合引用计数实现了PolledBuffer的用法,即池化。当引用计数等于0时,Netty将缓冲区回收到池中,并且它将在下一个缓冲区应用程序中的某个点被重用。
总结
实际上,Netty本质上是Reactor模式的实现,选择器作为复用器,EventLoop作为中继器,Pipeline作为事件处理程序。然而,与一般的反应器不同,Netty使用序列化,并在管道中使用责任链模式。
与NIO中的缓冲区相比,Netty中的缓冲区进行了优化,大大提高了性能。这就是Java中netty原则的细节。更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。