- BIO,同步阻塞IO,阻塞整个步骤,如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接。
- NIO,同步非阻塞IO,阻塞业务处理但不阻塞数据接收,适用于高并发且处理简单的场景,比如聊天软件。
- 多路复用IO,他的两个步骤处理是分开的,也就是说,一个连接可能他的数据接收是线程a完成的,数据处理是线程b完成的,他比BIO能处理更多请求,但是比不上NIO,但是他的处理性能又比BIO更差,因为一个连接他需要两次system call,而BIO只需要一次,所以这种IO模型应用的不多。
- 信号驱动IO,这种IO模型主要用在嵌入式开发,不参与讨论。
- 异步IO,他的数据请求和数据处理都是异步的,数据请求一次返回一次,适用于长连接的业务场景。
NIO模型:
- Acceptor注册Selector,监听accept事件
- 当客户端连接后,触发accept事件
- 服务器构建对应的Channel,并在其上注册Selector,监听读写事件
- 当发生读写事件后,进行相应的读写处理
还有一个问题,由于是非阻塞的,应用无法知道什么时候消息读完了,就存在了半包问题!
Reactor模型和AWT事件模型很像,就是将消息放到了一个队列中,通过异步线程池对其进行消费!
Reactor模型进化
Reactor中的组件
- Reactor:Reactor是IO事件的派发者。
- Acceptor:Acceptor接受client连接,建立对应client的Handler,并向Reactor注册此Handler。
- Handler:和一个client通讯的实体,按这样的过程实现业务的处理。一般在基本的Handler基础上还会有更进一步的层次划分, 用来抽象诸如decode,process和encoder这些过程。比如对Web Server而言,decode通常是HTTP请求的解析, process的过程会进一步涉及到Listener和Servlet的调用。业务逻辑的处理在Reactor模式里被分散的IO事件所打破, 所以Handler需要有适当的机制在所需的信息还不全(读到一半)的时候保存上下文,并在下一次IO事件到来的时候(另一半可读了)能继续中断的处理。为了简化设计,Handler通常被设计成状态机,按GoF的state pattern来实现。
对应上面的NIO代码来看:
- Reactor:相当于有分发功能的Selector
- Acceptor:NIO中建立连接的那个判断分支
- Handler:消息读写处理等操作类
Reactor从线程池和Reactor的选择上可以细分为如下几种:
Reactor单线程模型
这个模型和上面的NIO流程很类似,只是将消息相关处理独立到了Handler中去了!
虽然上面说到NIO一个线程就可以支持所有的IO处理。但是瓶颈也是显而易见的!我们看一个客户端的情况,如果这个客户端多次进行请求,如果在Handler中的处理速度较慢,那么后续的客户端请求都会被积压,导致响应变慢!所以引入了Reactor多线程模型!
Reactor多线程模型
Reactor多线程模型就是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程!这样的话,客户端的请求会直接被丢到线程池中,客户端发送请求就不会堵塞!
但是当用户进一步增加的时候,Reactor会出现瓶颈!因为Reactor既要处理IO操作请求,又要响应连接请求!为了分担Reactor的负担,所以引入了主从Reactor模型!
主从Reactor模型
主Reactor用于响应连接请求,从Reactor用于处理IO操作请求!
为什么使用netty
netty底层基于jdk的NIO,我们为什么不直接基于jdk的nio或者其他nio框架?下面是我总结出来的原因
1.使用jdk自带的nio需要了解太多的概念,编程复杂
2.netty底层IO模型随意切换,而这一切只需要做微小的改动
3.netty自带的拆包解包,异常检测等机制让你从nio的繁重细节中脱离出来,让你只需要关心业务逻辑
4.netty解决了jdk的很多包括空轮训在内的bug
5.netty底层对线程,selector做了很多细小的优化,精心设计的reactor线程做到非常高效的并发处理
6.自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
7.netty社区活跃,遇到问题随时邮件列表或者issue
8.netty已经历各大rpc框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大
Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。
Netty核心组件:
Bootstrap,一个Netty应用通常由一个Bootstrap开始,它主要作用是配置整个Netty程序,串联起各个组件。
-
哪些中间件用到了netty?
RPC(pigeon、dubbo、HSF)
Hadoop、Spark
MQ(swallow、RocketMQ)
Zookeeper等
几乎所有的基于java的分布式中间件都是采用netty作为通信工具的,(ps:redis是用c写的,采用了跟netty相同的epoll模型)
netty的架构
1.3 netty的组件
Bootstrap:netty的辅助启动器,netty客户端和服务器的入口,Bootstrap是创建客户端连接的启动器,ServerBootstrap是监听服务端端口的启动器,跟tomcat的Bootstrap类似,程序的入口。
Channel:关联jdk原生socket的组件,常用的是NioServerSocketChannel和NioSocketChannel,NioServerSocketChannel负责监听一个tcp端口,有连接进来通过boss reactor创建一个NioSocketChannel将其绑定到worker reactor,然后worker reactor负责这个NioSocketChannel的读写等io事件。
EventLoop:netty最核心的几大组件之一,就是我们常说的reactor,人为划分为boss reactor和worker reactor。通过EventLoopGroup(Bootstrap启动时会设置EventLoopGroup)生成,最常用的是nio的NioEventLoop,就如同EventLoop的名字,EventLoop内部有一个无限循环,维护了一个selector,处理所有注册到selector上的io操作,在这里实现了一个线程维护多条连接的工作。
ChannelPipeline:netty最核心的几大组件之一,ChannelHandler的容器,netty处理io操作的通道,与ChannelHandler组成责任链。write、read、connect等所有的io操作都会通过这个ChannelPipeline,依次通过ChannelPipeline上面的ChannelHandler处理,这就是netty事件模型的核心。ChannelPipeline内部有两个节点,head和tail,分别对应着ChannelHandler链的头和尾。
ChannelHandler:netty最核心的几大组件之一,netty处理io事件真正的处理单元,开发者可以创建自己的ChannelHandler来处理自己的逻辑,完全控制事件的处理方式。ChannelHandler和ChannelPipeline组成责任链,使得一组ChannelHandler像一条链一样执行下去。ChannelHandler分为inBound和outBound,分别对应io的read和write的执行链。ChannelHandler用ChannelHandlerContext包裹着,有prev和next节点,可以获取前后ChannelHandler,read时从ChannelPipeline的head执行到tail,write时从tail执行到head,所以head既是read事件的起点也是write事件的终点,与io交互最紧密。
Unsafe:顾名思义这个类就是不安全的意思,但并不是说这个类本身不安全,而是不要在应用程序里面直接使用Unsafe以及他的衍生类对象,实际上Unsafe操作都是在reactor线程中被执行。Unsafe是Channel的内部类,并且是protected修饰的,所以在类的设计上已经保证了不被用户代码调用。Unsafe的操作都是和jdk底层相关。EventLoop轮询到read或accept事件时,会调用unsafe.read(),unsafe再调用ChannelPipeline去处理事件;当发生write事件时,所有写事件都会放在EventLoop的task中,然后从ChannelPipeline的tail传播到head,通过Unsafe写到网络中。
2.2.1 select模型
select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有socket描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
2.3.2 poll模型
与select不同,poll使用一个 pollfd的指针实现。
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
2.4.3 epoll模型
相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。
epoll的优点
epoll监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目
IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。
netty的nio模型就是epoll机制
ServerBootstrapAcceptor的作用是:监听到accept事件,生成NioSocketChannel对象,然后将这个对象注册到worker reactor上,让worker reactor负责这条连接的读写。
boss reactor处理accept事件,将新连接封装成channel对象扔给worker reactor,worker reactor处理连接的读写事件。
NioEventLoop是一个单线程的,所以,一个 NioEventLoop 其实和一个特定的线程绑定, 并且在其生命周期内, 绑定的线程都不会再改变。
eventLoop是一个Executor,可以调用execute给eventLoop提交任务,NioEventLoop会在runAllTasks执行。NioEventLoop内部分为普通任务和定时任务,在执行过程中,NioEventLoop会把过期的定时任务从scheduledTaskQueue转移到taskQueue中,然后执行taskQueue中的任务,同时每隔64个任务检查是否该退出任务循环。
7.1 netty的byteBuf
Netty 使用自建的 buffer API,而不是使用 NIO 的 ByteBuffer 来表示一个连续的字节序列。与 ByteBuffer 相比这种方式拥有明显的优势。
netty中ByteBuf的缓冲区的优势:
需要的话,可以自定义buffer类型;
通过组合buffer类型,可实现透明的零拷贝;
提供动态的buffer类型,如StringBuffer一样,容量是按需扩展;
无需调用flip()方法;
通常比ByteBuffer快。
Netty的zero-copy则是完全在用户态,Netty通过ByteBuf.slice以及Unpooled.wrappedBuffer等方法拆分、合并Buffer无需拷贝数据
堆内存和直接内存
1) 堆内存字节缓冲区(HeapByteBuf)
内存的分配和回收速度快,可以被JVM自动回收,缺点是如果进行socket的I/O读写,需要额外做一次内存复制,将堆内存对应的字节缓冲区复制到内核Channel中,性能会有一定的下降。
2) 直接内存字节缓冲区(DirectByteBuf)
非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度会慢一些,但是将他写入或者从SocketChannel中读取出是,由于少了一次内存复制,速度比堆内存快。
netty写数据用的ChannelOutboundBuffer就是直接内存缓冲区,这样在写入socket缓冲区的时候少了一次内存复制,速度更快;在读数据时,netty使用的PooledUnsafeDirectByteBuf也是直接内存缓冲区,同样减少内存复制,速度更快。
-
ServerBootstrapAcceptor 事件,主要做一件事件,那就是把childGroup 和SocketChannel 相关联。 之后,读取写入数据就交由EventLoop来处理。
dfdf
f
参考:
https://blog.csdn.net/ningdunquan/article/details/80342970
https://www.cnblogs.com/wjlstation/p/8972895.html