了解Dubbo先了解Netty!
1、Netty是什么
2、Netty线程模型
3、EventLoopGroup / EventLoop
4、ChannelPipeline
5、Buffer
6、总结
1. Netty是什么
Netty是⼀个⾼性能、异步事件驱动的NIO框架,基于JAVA NIO提供的API实现。它提供了对TCP、UDP和⽂件传输的⽀持
作为⼀个异步NIO框架,Netty的所有IO操作都是异步⾮阻塞的,通过Future-Listener机制,⽤户可以⽅便的主动获取或者通过通知机制获得IO操作结果。
作为当前最流⾏的NIO框架,Netty在互联⽹领域、⼤数据分布式计算领域、游戏⾏业、通信⾏业等获得了⼴泛的应⽤,⼀些业界著名的开源组件也基于Netty的NIO框架构建。
2. Netty线程模型
在JAVA NIO⽅⾯Selector给Reactor模式提供了基础,Netty结合Selector和Reactor模式设计了⾼效的线程模型。先来看下Reactor模式:
2.1 Reactor模式
Wikipedia这么解释Reactor模型:
“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。
⾸先Reactor模式⾸先是事件驱动的,有⼀个或者多个并发输⼊源,有⼀个Server Handler和多个Request Handlers
这个Service Handler会同步的将输⼊的请求多路复⽤的分发给相应的Request Handler。
可以如下图所⽰:
reactor then从结构上有点类似⽣产者和消费者模型,即⼀个或多个⽣产者将事件放⼊⼀个Queue中,⽽⼀个或者多个消费者主动的从这个队列中poll事件来处理;
⽽Reactor模式则没有Queue来做缓冲,每当⼀个事件输⼊到Service Handler之后,该Service Handler会主动根据不同的Evnent类型将其分发给对应的Request Handler来处理。
2.2 Reator模式的实现
关于Java NIO 构造Reator模式,Doug lea在《Scalable IO in Java》中给了很好的阐述,这⾥截取PPT对Reator模式的实现进⾏说明
1.第⼀种实现模型如下:
这是最简单的Reactor单线程模型,由于Reactor模式使⽤的是异步⾮阻塞IO,所有的IO操作都不会被阻塞,理论上⼀个线程可以独⽴处理所有的IO操作。
这时Reactor线程是个多⾯⼿,负责多路分离套接字,Accept新连接,并分发请求到处理链中。
对于⼀些⼩容量应⽤场景,可以使⽤到单线程模型。但对于⾼负载,⼤并发的应⽤却不合适,主要原因如下:
1. 当⼀个NIO线程同时处理成百上千的链路,性能上⽆法⽀撑,即使NIO线程的CPU负荷达到100%,也⽆法完全处理消息
2. 当NIO线程负载过重后,处理速度会变慢,会导致⼤量客户端连接超时,超时之后往往会重发,更加重了NIO线程的负载。
3. 可靠性低,⼀个线程意外死循环,会导致整个通信系统不可⽤
为了解决这些问题,出现了Reactor多线程模型。
2.Reactor多线程模型:
相⽐上⼀种模式,该模型在处理链部分采⽤了多线程(线程池)。
在绝⼤多数场景下,该模型都能满⾜性能需求。但是,在⼀些特殊的应⽤场景下,如服务器会对客户端的握⼿消息进⾏安全认证。这类场景下,单独的⼀个Acceptor线程可能会存在性能不⾜的问题。
为了解决这些问题,产⽣了第三种Reactor线程模型
3.Reactor主从模型
该模型相⽐第⼆种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建⽴的socket分派给
subReactor。
subReactor负责多路分离已连接的socket,读写⽹络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。
2.3 Netty模型
2.2中说完了Reactor的三种模型,那么Netty是哪⼀种呢?
其实Netty的线程模型是Reactor模型的变种,那就是去掉线程池的第三种形式的变种,这也是Netty NIO的默认模式。
Netty中Reactor模式的参与者主要有下⾯⼀些组件:
1. Selector
2. EventLoopGroup/EventLoop
3. ChannelPipeline
Selector即为NIO中提供的SelectableChannel多路复⽤器,充当着demultiplexer的⾓⾊,这⾥不再赘述;下⾯对另外两种功能和其在Netty 之Reactor模式中扮演的⾓⾊进⾏介绍。
3.EventLoopGroup / EventLoop
当系统在运⾏过程中,如果频繁的进⾏线程上下⽂切换,会带来额外的性能损耗。
多线程并发执⾏某个业务流程,业务开发者还需要时刻对线程安全保持警惕,哪些数据可能会被并发修改,如何保护?这不仅降低了开发效率,也会带来额外的性能损耗。
为了解决上述问题,Netty采⽤了串⾏化设计理念
从消息的读取、编码以及后续Handler的执⾏,始终都由IO线程EventLoop负责,这就意味着整个流程不会进⾏线程上下⽂的切换,数据也不会⾯临被并发修改的风险。这也解释了为什么Netty线程模型去掉了Reactor主从模型中线程池。
EventLoopGroup是⼀组EventLoop的抽象,EventLoopGroup提供next接⼝,可以从⼀组EventLoop⾥⾯按照⼀定规则获取其中⼀个EventLoop来处理任务
对于EventLoopGroup这⾥需要了解的是在Netty中,在Netty服务器编程中我们需要BossEventLoopGroup和WorkerEventLoopGroup两个EventLoopGroup来进⾏⼯作。
通常⼀个服务端⼝即⼀个ServerSocketChannel对应⼀个Selector和⼀个EventLoop线程,也就是说BossEventLoopGroup的线程数参数为1。
BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进⾏IO处理。
EventLoop的实现充当Reactor模式中的分发(Dispatcher)的⾓⾊。
4.ChannelPipeline
ChannelPipeline其实是担任着Reactor模式中的请求处理器这个⾓⾊。
ChannelPipeline的默认实现是DefaultChannelPipeline,DefaultChannelPipeline本⾝维护着⼀个⽤户不可见的tail和head的ChannelHandler,他们分别位于链表队列的头部和尾部。tail在更上层的部分,⽽head在靠近⽹络层的⽅向。
在Netty中关于ChannelHandler有两个重要的接⼝,ChannelInBoundHandler和ChannelOutBoundHandler。
inbound可以理解为⽹络数据从外部流向系统内部,⽽outbound可以理解为⽹络数据从系统内部流向系统外部。
⽤户实现的ChannelHandler可以根据需要实现其中⼀个或多个接⼝,将其放⼊Pipeline中的链表队列中,ChannelPipeline会根据不同的IO 事件类型来到相应的Handler来处理
同时链表队列是责任链模式的⼀种变种,⾃上⽽下或⾃下⽽上所有满⾜事件关联的Handler都会对事件进⾏处理。
ChannelInBoundHandler对从客户端发往服务器的报⽂进⾏处理,⼀般⽤来执⾏半包/粘包,解码,读取数据,业务处理等;ChannelOutBoundHandler对从服务器发往客户端的报⽂进⾏处理,⼀般⽤来进⾏编码,发送报⽂到客户端。
下图是对ChannelPipeline执⾏过程的说明:
5.Buffer
Netty提供的经过扩展的Buffer相对NIO中的有个许多优势,作为数据存取⾮常重要的⼀块,我们来看看Netty中的Buffer有什么特点。
1.ByteBuf读写指针
在ByteBuffer中,读写指针都是position,⽽在ByteBuf中,读写指针分别为readerIndex和writerIndex
直观看上去ByteBuffer仅⽤了⼀个指针就实现了两个指针的功能,节省了变量,但是当对于ByteBuffer的读写状态切换的时候必须要调⽤flip⽅法,⽽当下⼀次写之前,必须要将Buffe中的内容读完,再调⽤clear⽅法。
每次读之前调⽤flip,写之前调⽤clear,这样⽆疑给开发带来了繁琐的步骤,⽽且内容没有读完是不能写的,这样⾮常不灵活。
相⽐之下我们看看ByteBuf,读的时候仅仅依赖readerIndex指针,写的时候仅仅依赖writerIndex指针,不需每次读写之前调⽤对应的⽅法,⽽且没有必须⼀次读完的限制。
2.零拷贝
Netty的接收和发送ByteBuffer采⽤DIRECT BUFFERS,使⽤堆外直接内存进⾏Socket读写,不需要进⾏字节缓冲区的⼆次拷贝。
如果使⽤传统的堆内存(HEAP BUFFERS)进⾏Socket读写,JVM会将堆内存Buffer拷贝⼀份到直接内存中,然后才写⼊Socket中。
相⽐于堆外直接内存,消息在发送过程中多了⼀次缓冲区的内存拷贝。
Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,⽤户可以像操作⼀个Buffer那样⽅便的对组合Buffer进⾏操作,避免了传统通过内存拷贝的⽅式将⼏个⼩Buffer合并成⼀个⼤的Buffer。
Netty的⽂件传输采⽤了transferTo⽅法,它可以直接将⽂件缓冲区的数据发送到⽬标Channel,避免了传统通过循环write⽅式导致的内存拷贝问题。
3.引⽤计数与池化技术
在Netty中,每个被申请的Buffer对于Netty来说都可能是很宝贵的资源,因此为了获得对于内存的申请与回收更多的控制权,Netty⾃⼰根据引⽤计数法去实现了内存的管理。
Netty对于Buffer的使⽤都是基于直接内存(DirectBuffer)实现的,⼤⼤提⾼I/O操作的效率
然⽽DirectBuffer和HeapBuffer相⽐之下除了I/O操作效率⾼之外还有⼀个天⽣的缺点,即对于DirectBuffer的申请相⽐HeapBuffer效率更低
因此Netty结合引⽤计数实现了PolledBuffer,即池化的⽤法,当引⽤计数等于0的时候,Netty将Buffer回收致池中,在下⼀次申请Buffer的没某个时刻会被复⽤。
6、总结
Netty其实本质上就是Reactor模式的实现,Selector作为多路复⽤器,EventLoop作为转发器,Pipeline作为事件处理器。
但是和⼀般的Reactor不同的是,Netty使⽤串⾏化实现,并在Pipeline中使⽤了责任链模式。
Netty中的buffer相对有NIO中的buffer⼜做了⼀些优化,⼤⼤提⾼了性能。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。