1.Netty介绍
1.1原生NIO存在的问题
1.NIO的类库和API复杂,使用麻烦需要熟练掌握Selector,ServerSocketChannel,SocketChannel,ByteBuffer
2.需要熟悉多线程编程,因为NIO编程涉及到了Reactor模式,需要对多线程跟网络编程有扎实的基础才能编写出高质量的NIO程序。
3.开发工作量跟难度都非常大,例如客户端面临断连重连,网络闪断,半包读写,失败缓存,网络拥塞和异常流的处理等。
4.JDK NIO的bug:
Epoll Bug
,它会导致Selector空轮询从而导致CPU 100%(在Selector轮询当前是否有IO事件,根据JDK NIO API 描述,Selector的select方法会一直阻塞,直到IO事件到达或超时,但在linux上有时会出现问题,在某些场景下select方法会直接返回,即使没有IO事件到达并且超时 这就是Epoll Bug,它会导致线程陷入死循环让CPU飙升到100%,极大的影响了系统的可靠性。到目前为止,JDK暂未解决此问题)
1.2概述
Netty是由JBOSS提供的一个java开源框架,Netty提供异步的,基于事件驱动的网络应用程序框架,用以快速开发高性能,高可靠的网络IO程序,Netty是基于NIO的网络编程框架,使用Netyy可以快速,简单的开发一个网络应用,简化和流程化了NIO的开发过程,Netty强大之处:零拷贝,可拓展时间模型,支持HTTP TCP UDP WebSocket等协议,提供安全传输,压缩,大文件传输,编解码器支持等(Elasticsearch,Dubbo框架内部都采用了Netty)
2.线程模型
2.1线程模型基本介绍
目前存在的线程模型:
1.传统阻塞IO服务模型
2.Reactor模型
根据Reactor的数量和处理资源线程的数量不同可分为三种
1.单Reactor单线程,通过一个或多个输入同时传递服务处理器模式,服务端程序处理传入多个请求,并将它们同步分派到相应的处理线程,所以Reactor模式也称Dispatcher模式,Reactor模式使用IO复用监听事件,收到事件后,分发给某个线程这点就是网络服务器高并发处理关键。
Selector可以实现应用程序通过一个阻塞对象监听多路连接请求,Reactor对象通过Selector监控客户端请求事件,收到事件后通过Dispatch进行分发,建立连接请求事件,由Acceptor通过过Accept处理连接请求,然后创建一个handle对象进行处理连接完成后的后续业务处理。
优点:
模型简单,没有多线程,进程通信,竞争的问题,全部在一个线程中完成
缺点:
只有一个线程无法发挥多核CPU性能,并且由于是单线程出现意外终止或者死循环,会导致整个系统模块不可用,造成节点故障。
2.单Reactor多线程
Reactor对象通过Selector监控客户端请求事件,收到事件后,通过Dispatch进行分发
建立连接请求,则由Acceptor通过Accept处理连接请求
如果不是连接请求,则由Reactor分发调用连接对应的handle来处理
handle只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务
worker线程池会分配独立线程完成真正的业务,并将结果返回给handle,handle接收到响应后,通过send将结果返回给client。
优点:
可以充分利用多核CPU的处理能力
缺点:
多线程的数据共享和访问比较复杂,reactor处理所有时间的监听和响应,在单线程运行在高并发场景容易出现性能瓶颈
3.主从Reactor多线程(服务器1+M+N线程模式,该模式开发的服务器包含一个或多个,连接建立线程+M个IO线程+N个业务处理线程,这是业界成熟的服务器程序设计模式)
1.Reactor主线程MainReactor对象通过Select监听客户端连接事件,收到事件后通过Acceptor处理客户端连接事件
2.当Acceptor处理完客户端连接事件之后(与客户端建立好Socket连接),MainReactor将连接分配给SubReactor(MainReactor只负责监听客户端连接请求,和客户端连接之后将连接交由SubReactor监听后面的IO事件)
3.SubReactor将连接加入到自己的连接队列进行监听,并创建Handler对各种事件进行处理
4.当连接上有新事件发生的时候,SubReactor就会调用对应的Handler处理
5.Handler通过read从连接上读取请求数据,将请求数据分发给Worker线程池进行业务处理
6.Worker线程池会分配独立线程来完成真正的业务处理,并将处理结果返回给Handler,Handler通过send向客户端发送响应数据
7.一个MainReactor可以对应多个SubReactor(一个MainReactor线程可以对应多个SubReactor线程)
优点:
1.MainReactor线程和SubReactor线程的数据交互简单职责明确(MainReactor线程只需要接收新连接,SubReactor线程完成后续的业务处理)
2.MainReactor线程只需要把新连接传给SubReactor线程,SubReactor线程无需返回数据
3.多个SubReactor线程能够对应更高的并发
缺点:
编程复杂度高
3.Netty线程模型
netty的设计模式是基于主从Reactor多线程模式,并在此基础上做了一些改进
3.1简单版Netty模型
1.BossGroup线程维护Selector,ServerSocketChannel注册到Selector上,只关注建立请求事件(主Reactor)
2.当接收到来自客户端的连接建立请求事件的时候,通过SeverSocketChannel.accept方法获得对应的SocketChannel,并封装成NioSocketChannel注册到WorkerGroup线程中的Selector,每个线程运行在一个线程中(从Reactor)
3.当WorkerGroup线程中的Selector监听到自己的IO事件后,就调用Handler进行处理
3.2进阶版Netty模型
1.两组线程池:BossGroup(负责和客户端建立连接)和WorkerGroup(负责处理连接上的写和读)
2.BossGroup和WorkerGroup含有多个不断循环的执行事件处理的线程,每个线程都包含一个Selector用于监听注册在其上的Channel
3.每个BossGroup中的线程循环执行以下三个步骤:
3.1:轮询注册在其上的ServerSocketChannel的accept事件(OP_ACCEPT事件)
3.2:处理accept事件,与客户端建立连接,生成一个NioSocketChannel,并将其注册到WorkerGroup中的某个线程上的Selector
3.3:再去以此循环处理任务队列中的下一个事件
4.每个WorkerGroup中的线程循环执行以下三个步骤:
4.1:轮询注册在其上的NioSocketChannel的read/write事件(OP_READ/OP_WRITE事件)
4.2:在对应的NioSocketChannel上处理read/write事件
4.3:再去以此循环处理任务队列中的下一个事件
3.3详细版Netty模型
1.Netty抽象出两组线程池:BossGroup和WorkerGroup,也可以叫做BossNioEventLoopGroup和WorkerNioEventGroup,每个线程中都有NioEventLoo线程,BossGroup中的线程专门负责和客户端建立连接,WorkerGroup线程专门负责处理连接上的读写。
2.NioEventLoopGroup相当于一个事件循环组,每个组中含有多个事件循环,每个事件循环就是一个NioEventLoop
3.NioEvenLoop表示一个不断循环的执行事件处理的线程,每个NioEventLoop都包含一个Selector,用于监听注册在其上的Socket网络连接(Channel)
4.NioEentLoopGroup可以含有多个线程(既可以含有多个NioEventLoop)
5.每个BossNioEventLoop中循环执行一下三个步骤:
1.selector:轮询注册在其上的ServiceSocketChannel的accept事件(OP_ACCEPT事件)
2.processSelectedKeys:处理accept事件,与客户端建立连接生成一个NioSocketChannel并将其注册到某个WorkerNioEventLooo上的Selector上
3.runAllTaks:再去以此循环处理任务队列中的其他任务
6.每个workerNioEventLoop中的循环执行一下三个步骤:
1.select:轮询注册在其上的NioSocketChannel的read/write事件(OP_READ/OP_WRITE)
2.processSelectedKeys:在对应的NioSocketChannel上处理read/write事件
3.runAllTasks:再去 以此循环处理任务队列中的其他任务
在以上两个processSelectedKeys步骤中,会使用Pipeline,Pipeline中引用了Channel,既通过Pipeline可以获取对应的 Channel,(Pipeline中维护了很多处理器,如:拦截处理器,过滤处理器,自定义处理器等)
4.Netty解码器
java的编解码
编码也成为序列化,将对象序列化字节数组,用于网络传输,数据持久化或者其他用途
解码也成为反序列化,把从网络,磁盘等读取到的字节数组还原成对象,一边后续的业务逻辑处理
Netty的编解码
在网络应用中需要实现某种解码器,将原始字节数据与自定义的消息对象进行相互转换,网络中的都是以字节码的数据形式来进行传输,服务器编码数据后发送到客户端,客户端需要对数据进行解码
Netty的编码器:将消息对象转成字节或其他序列形式在网络上传输
Netty的解码器:负责将消息从字节或者其他序列形式转成对象
解码器:
核心方法:decode()
编码器:
核心方法:encode()
5.Netty粘包和拆包
粘包和拆包是TCP网络编程不可避免的,无论是读取消息还是接受消息都要考虑TCP底层的粘包和拆包机制。
TCP粘包和拆包产生的原因:
数据从发送方到接收方需要经过操作系统的缓冲区,而此时粘包和拆包问题就在于此,粘包产生的原因可以理解过缓冲区数据堆积导致多个请求数据粘在一起,拆包是发送的数据大于缓冲区进行拆分处理。
针对拆包和粘包Netty给出了四个解决方案
1.固定长度的拆包器(FixedLengthFrameDecoder),按照固定长度进行拆分
2.行拆包器(LinBasedFrameDecoder),按照换行符进行拆分
3.分隔符拆包器(DelimiterBasedFrameDecoder),通过自定义分隔符进行拆包
4.基于数据包长度的拆分器(LengthFieldBasedFrameDecoder),将应用层数据包的长度作为接收端应用层数据包的拆分依据,按照应用层数据包的大小进行拆包(前提:应用层协议中包含数据包的长度)
结束语:
欢迎补充跟纠正…
共勉: 道阻且长 行则将至