扑街前言:上篇文章说了I/O,那都是为了这个做铺垫netty才是重点,学习这个人都麻了,不知道说什么前言了,就这样吧!(认识到自己是菜鸟的不知道多少天)
Netty的概述
Netty是由JBOSS提供的一个Java开源框架,现为Github上的独立项目。Netty提供非阻塞的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
- 本质:网络应用程序框架;
- 实现:异步、事件驱动;
- 特性:高性能、可维护、快速开发;
- 用途:开发服务器和客户端。
Netty的核心架构
- 核心:可扩展的事件模型;统一的通信api,简化了通信编码;零拷贝机制与丰富的字节缓冲区。
- 传输服务:支持socket以及datagram(数据报);http传输服务;In-VM Pipe(管道协议,是jvm的一种进程)。
- 协议支持:http以及websocket;SSL安全套接字协助支持;Google Protobuf(序列化框架);支持zlib、gzip压缩;支持大文件的传输;RTSP(实时流传输协议,是TCP/IP协议体系中的一个应用层协议);支持二进制协议并且提供了完整的单元测试。
开发网络应用不选JDK原生API 而选Netty的理由
API:
Netty更友好更强大:1、JDK中NIO的一些API功能薄弱且复杂,Netty隔离了JDK中NIO的实现变化及实现细节譬如:ByteBuffer -> ByteBuf主要负责从底层的IO中读取数据到ByteBuf,然后传递给应用程序,应用程序处理完之后封装为ByteBuf,写回给IO。
并发编程:
Netty自身线程安全:使用JDK原生API需要对多线程要很熟悉, 因为N I O 涉及到Reactor设计模式,得对里面的原理要相当的熟悉。
高可用:
完整的高可用机制:JDK原生方式要实现高可用,需要自己实现断路重连、半包读写、粘包处理、失败缓存处理等相关操作,而Netty则做的更多,它解决了传输的一些问题譬如粘包半包现象,它支持常用的应用层协议,完善的断路重连,idle等异常处理。
Bug:
JDK NIO BUG:JDK的NIO存在bug,如经典的epoll bug,会导致CPU 100%而Netty封装的更完善。
Netty对三种IO的支持
上篇文章我们已经说过了三种IO的基本概念,目前我们再了解一下Netty对三种IO的支持,如下截图。
Reactor 线程模型
Reactor线程模型不是Java的专属,也不是Netty专属,它其实是一种并发编程模型,是一种思想,具有指导意义。
Reactor模型中定义了三种角色:
- Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler。新的时间包含连接建立就绪、读就绪、写就绪等。
- Acceptor:处理客户端新连接,并分派请求到处理器链中。
- Handler:将自身与事件绑定,执行费阻塞读/写人物,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel。
单Reactor - 单线程模型
NIO下Reactor单线程,所有的接受连接,处理数据的相关操作都在一个线程中完成,性能上有瓶颈。可以参考上面文章实现的NIO服务。
单Reactor - 多线程模型
把比较消耗时的数据的编解码运算操作放入线程池中执行,虽然提升了性能但是还不是最好的方式。
主从Reactor - 多线程
主从多线程,对于服务器来说,接收客户端的连接是比较重要的,因此将这部分操作单独用线程去操作。
主从Reactor工作模式
这种模式的基本工作流程为:
- Reactor主线程MainReactor对象通过select监听客户端连接事件,收到事件后,通过Acceptor处理客户端连接事件。
- 当Acceptor处理完客户端连接事件之后(与客户端建立号socket连接),MainReactor将连接分配给subReactor。(即:MainReactor只负责监听客户端的请求,和客户端建立连接后将连接交给subReactor监听后面的IO事件。)
- subReactor将连接加入到自己的连接队列进行监听,并创建Handler对各种事件进行处理。
- 当连接上有新事件发生的时候,subReactor将会调用对应的Handler处理。
- Handler通过read从连接上读取请求数据,将请求数据分发给Worker线程池进行业务处理。
- worker线程池会分配独立的线程来完成真正的业务处理,并将处理结果返回给handler。Handler通过send向客户端发送响应数据。
- 一个MainReactor可以对应多个subReactor,即一个MainReactor线程可以对应多个subReactor线程。
主从Reactor优势
这种模式的优势如下:
- MainReactor 线程与SubReactor 线程的数据交互简单职责明确,MainReactor 线程只需要接受新连接,SubReactor 线程完成后续的业务处理。
- MainReactor 线程与SubReactor 线程的数据交互简单,MainReactor 线程只需要把新连接传给SubReactor 线程,SubReactor 线程无需返回数据。
- 多个SubReactor 线程能够应对更高的并发请求。
这种模式的缺点是编程复杂度高。但是由于优点明显,在许多项目中被广泛使用,包括Nginx、Memcachend、Netty等。
这种模式也被叫做服务器的 1+M+N线程模式,即使用改模式开发的服务器包含一个(或多个,1只是表示相对较少)连接建立线程 + M 个IO 线程 + N 个业务处理线程。这是业界成熟的服务器程序设计模式。
Netty中的Reactor实现
- Netty抽象出两组线程池:Boss EventLoopGroup和Worker EventLoopGroup,每个线程池中都有 EventLoopGroup 线程(可以是OIO,NIO,AIO)。Boss EventLoopGroup中的线程专门负责处理和客户端建立连接,Worker EventLoopGroup 中的线程专门负责处理连接上的读写,EventLoopGroup 相当于一个事件循环组,这个组中含有锁多个事件循环。
- EventLoop 表示一个不断循环的执行事件处理的线程,每个EventLoop 都含有一个selector,用于监听注册在其上的socket 网络连接(channel)。
- 每一个Boss EventLoopGroup 中循环执行以下三个步骤:a、select:轮询注册在其上的serverSocketChannel 的 accept 事件(OP_ACCEPT 事件);b、processSeleckendKeys:处理 accept 事件,与客户端建立连接,生产一个socketChannel,并将其注册到Worker EventLoop 上的某个selector 上;c、runAllTasks:再去以此循环处理任务队列中的其他任务。
- 每个Worker EventLoop 中循环执行这三个步骤:a、select:轮询注册在其上的socketChannel 的read / write 事件(OP_READ / OP_WRITE 事件);b、processSeleckendKeys:在对应的socketChannel 上处理read / write 事件;c、runAllTasks:再去以此循环处理任务队列中的其他任务。
- 在以上两个processSeleckendKeys步骤中,会使用Pipeline(管道),Pipeline 中引用了channel,即通过Pipeline 可以获取对应的Channel,Pipeline 中维护了很多的处理器(拦截处理器、过滤处理器、自定义处理器等)。
Netty 线程模型其他事项
- Netty 的线程模型基于主从多Reactor模型。通常由一个线程负责处理OP_ACCEPT事件,拥有 CPU 核数的两倍的IO线程处理读写事件;
- 一个通道的IO操作会绑定在一个IO线程中,而一个IO线程可以注册多个通道;
- 在一个网络通信中通常会包含网络数据读写,编码、解码、业务处理。默认情况下网络数据读写,编码、解码等操作会在IO线程中运行,但也可以指定其他线程池;
- 通常业务处理会单独开启业务线程池(看业务类型),但也可以进一步细化,例如心跳包可以直接在IO线程中处理,而需要再转发给业务线程池,避免线程切换;
- 在一个IO线程中所有通道的事件是串行处理的;
- 通常业务操作会专门开辟一个线程池,那业务处理完成之后,如何将响应结果通过 IO 线程写入到网卡中呢?业务线程调用 Channel对象的 write 方法并不会立即写入网络,只是将数据放入一个待写入缓存区,然后IO线程每次执行事件选择后,会从待写入缓存区中获取写入任务,将数据真正写入到网络中。
Pipeline中的ChannelPipeline & ChannelHandler
处理器ChannelHandler:ChannelPipeline 提供了 ChannelHandler 链的容器。以服务端程序为例,客户端发送过来的数据要接收,读取处理,我们称数据是入站的,需要经过一系列Handler处理后;如果服务器想向客户端写回数据,也需要经过一系列Handler处理,我们称数据是出站的。ChannelPipeline 和 ChannelHandler 之间是相互绑定的,也就是一对一关系。
ChannelHandler 分类
inbound/outbound:对于数据的出站和入站,有着不同的ChannelHandler类型与之对应:ChannelInboundHandler 入站事件处理器、ChannelOutBoundHandler 出站事件处理器、ChannelHandlerAdapter提供了一些方法的默认实现,可减少用户对于ChannelHandler的编写、ChannelDuplexHandler:混合型,既能处理入站事件又能处理出站事件。从下面的关系图可以看出,如果我们需要自己的Handler的时候,读取事件可以继承ChannelInboundHandlerAbapter,写出事件可以继承ChannelOutBoundHandlerAbapter。
ChannelHandler 体系结构
inbound/outbound:inbound入站事件处理顺序(方向)是由链表的头到链表尾,outbound事件的处理顺序是由链表尾到链表头。inbound入站事件由netty内部触发,最终由netty外部的代码消费。outbound事件由netty外部的代码触发,最终由netty内部消费。
上述就对netty的一些简单说明,后续还有netty的核心组件剖析等一些的内容,本次就说这些。