BIO/NIO/AIO相关概念的介绍

同步非阻塞IO (NIO)html

NIO是基于事件驱动思想的,实现上一般采用Reactor(http://en.wikipedia.org/wiki/Reactor_pattern)模式,从程序角度而言,当发起IO的读或写操做时,是非阻塞的;当socket有流可读或可写入socket时,操做系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操做系统。对于网络IO而言,主要有链接创建、流读取及流写入三种事件、linux2.6之后的版本使用epoll(http://lse.sourceforge.net/epoll/index.html)方式实现NIO。linux

select/epoll的好处就在于单个process就能够同时处理多个网络链接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的全部socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:web

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”全部select负责的socket,当任何一个 socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并无太大的不一样,事实上,还更差一些。由于这里须要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。可是,用select的优点在于它能够同时处理多个connection。(多说一句。因此,若是处理的链接数不是很高的话,使用 select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优点并非对于单个链接能处理得更快,而是在于能处理更多的链接。)
在IOmultiplexing Model中,实际中,对于每个socket,通常都设置成为non-blocking,可是,如上图所示,整个用户的process实际上是一直被 block的。只不过process是被select这个函数block,而不是被socket IO给block。数据库

AIO,异步IO方式编程

AIO为异步IO方式,一样基于事件驱动思想,实现上一般采用Proactor模式(http://en.wikipedia.org/wiki/Proactor_pattern后端

从程序的角度而言,与NIO不一样,当进行读写操做时,只须直接调用API的read或write方法便可。这两种方法均为异步的,对于读操做而言,当有流可读取时,操做系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操做而言,当操做系统将write方法传递的流写入完毕时,操做系统主动通知应用程序。较之NIO而言,AIO一方面简化了程序出的编写,流的读取和写入都由操做系统来代替完成;另外一方面省去了NIO中程序要遍历事件通知队列(selector)的代价。Windows基于IOCP(http://en.wikipedia.org/wiki/Input/output_completion_port)实现了AIO,Linux目前只有基于epoll实现的AIO。网络

IO的方式一般分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO
 
在JDK1.4出来以前,咱们创建网络链接的时候采用BIO模式,须要先在服务端启动一个ServerSocket,而后在客户端启动Socket来对服务端进行通讯,默认状况下服务端须要对每一个请求创建一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,若是没有则会一直等待或者遭到拒绝请求,若是有的话,客户端会线程会等待请求结束后才继续执行。
BIO与NIO一个比较重要的不一样,是咱们使用BIO的时候每每会引入多线程,每一个链接一个单独的线程;而NIO则是使用单线程或者只使用少许的多线程,每一个链接共用一个线程。
NIO的最重要的地方是当一个链接建立后,不须要对应一个线程,这个链接会被注册到多路复用器上面,因此全部的链接只须要一个线程就能够搞定,当这个线程中的多路复用器进行轮询的时候,发现链接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC链接等),其实这个线程就被阻塞了,当并发上来的话,仍是会有BIO同样的问题。
 
HTTP/1.1出现后,有了Http长链接,这样除了超时和指明特定关闭的http header外,这个连接是一直打开的状态的,这样在NIO处理中能够进一步的进化,在后端资源中能够实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,而且在全局的地方保持住这个现场(哪一个链接的哪一个请求等),这样前面的线程仍是能够去接受其余的请求,然后端的应用的处理只须要执行队列里面的就能够了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方获得现场,产生响应,这个就实现了异步处理。多线程

BIO是一个链接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
并发

按照《Unix网络编程》的划分,IO模型能够分为:阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO。如何区分呢?首先一个IO操做其实分红了两个步骤:发起IO请求和实际的IO操做,同步IO和异步IO的区别就在于第二个步骤是否阻塞,若是实际的IO读写阻塞请求进程,那么就是同步IO,所以阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,若是不阻塞,而是操做系统帮你作完IO操做再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,若是阻塞直到完成那么就是传统的阻塞IO,若是不阻塞,那么就是非阻塞IO运维

Reactor: send(msg) -> 消息队列是否为空,若是为空-> 向Reactor注册OP_WRITE,而后返回 -> Reactor select -> 触发Writable,通知用户线程去处理->先注销Writable(不少人遇到的cpu 100%的问题就在于没有注销),处理Writeable,若是没有彻底写入,继续注册OP_WRITE。注意到,写入的工做仍是用户线程在处理。

Proactor: send(msg) ->消息队列是否为空,若是为空,发起read异步调用,并注册CompletionHandler,而后返回。-> 操做系统负责将你的消息写入,并返回结果(写入的字节数)给Proactor->Proactor派发CompletionHandler。可见,写入的工做是操做系统在处理,无需用户线程参与。事实上在aio的API中,AsynchronousChannelGroup就扮演了Proactor的角色。

http://www.ibm.com/developerworks/cn/linux/l-async/

除了须要阻塞以外,select 函数所提供的功能(异步阻塞 I/O)与 AIO 相似。不过,它是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞。

 

AIO 简介

Linux 异步 I/O 是 Linux 内核中提供的一个至关新的加强。它是 2.6 版本内核的一个标准特性,可是咱们在 2.4 版本内核的补丁中也能够找到它。AIO 背后的基本思想是容许进程发起不少 I/O 操做,而不用阻塞或等待任何操做完成。稍后或在接收到 I/O 操做完成的通知时,进程就能够检索 I/O 操做的结果。

I/O 模型

在深刻介绍 AIO API 以前,让咱们先来探索一下 Linux 上可使用的不一样 I/O 模型。这并非一个详尽的介绍,可是咱们将试图介绍最经常使用的一些模型来解释它们与异步 I/O 之间的区别。图 1 给出了同步和异步模型,以及阻塞和非阻塞的模型。


图 1. 基本 Linux I/O 模型的简单矩阵

每一个 I/O 模型都有本身的使用模式,它们对于特定的应用程序都有本身的优势。本节将简要对其一一进行介绍。

同步阻塞 I/O

I/O 密集型与 CPU 密集型进程的比较

I/O 密集型进程所执行的 I/O 操做比执行的处理操做更多。CPU 密集型的进程所执行的处理操做比 I/O 操做更多。Linux 2.6 的调度器实际上更加偏心 I/O 密集型的进程,由于它们一般会发起一个 I/O 操做,而后进行阻塞,这就意味着其余工做均可以在二者之间有效地交错进行。

最经常使用的一个模型是同步阻塞 I/O 模型。在这个模型中,用户空间的应用程序执行一个系统调用,这会致使应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种再也不消费 CPU 而只是简单等待响应的状态,所以从处理的角度来看,这是很是有效的。

图 2 给出了传统的阻塞 I/O 模型,这也是目前应用程序中最为经常使用的一种模型。其行为很是容易理解,其用法对于典型的应用程序来讲都很是有效。在调用read 系统调用时,应用程序会阻塞并对内核进行上下文切换。而后会触发读操做,当响应返回时(从咱们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。而后应用程序就会解除阻塞(read 调用返回)。


图 2. 同步阻塞 I/O 模型的典型流程

从应用程序的角度来讲,read 调用会延续很长时间。实际上,在内核执行读操做和其余工做时,应用程序的确会被阻塞。

同步非阻塞 I/O

同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操做不会当即完成,read 操做可能会返回一个错误代码,说明这个命令不能当即知足(EAGAIN 或EWOULDBLOCK),如图 3 所示。


图 3. 同步非阻塞 I/O 模型的典型流程

非阻塞的实现是 I/O 命令可能并不会当即知足,须要应用程序调用许屡次来等待操做完成。这可能效率不高,由于在不少状况下,当内核执行这个命令时,应用程序必需要进行忙碌等待,直到数据可用为止,或者试图执行其余工做。正如图 3 所示的同样,这个方法能够引入 I/O 操做的延时,由于数据在内核中变为可用到用户调用 read 返回数据之间存在必定的间隔,这会致使总体数据吞吐量的下降。

异步阻塞 I/O

另一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,而后使用阻塞select 系统调用来肯定一个 I/O 描述符什么时候有操做。使 select 调用很是有趣的是它能够用来为多个描述符提供通知,而不只仅为一个描述符提供通知。对于每一个提示符来讲,咱们能够请求这个描述符能够写数据、有读数据可用以及是否发生错误的通知。


图 4. 异步阻塞 I/O 模型的典型流程 (select)

select 调用的主要问题是它的效率不是很是高。尽管这是异步通知使用的一种方便模型,可是对于高性能的 I/O 操做来讲不建议使用。

异步非阻塞 I/O(AIO)

最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会当即返回,说明 read 请求已经成功发起了。在后台完成读操做时,应用程序而后会执行其余处理操做。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成此次 I/O 处理过程。


图 5. 异步非阻塞 I/O 模型的典型流程

在一个进程中为了执行多个 I/O 请求而对计算操做和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差别。当一个或多个 I/O 请求挂起时,CPU 能够执行其余任务;或者更为常见的是,在发起其余 I/O 的同时对已经完成的 I/O 进行操做。

下一节将深刻介绍这种模型,探索这种模型使用的 API,而后展现几个命令。

 

异步 I/O 的动机

从前面 I/O 模型的分类中,咱们能够看出 AIO 的动机。这种阻塞模型须要在 I/O 操做开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 I/O 操做。同步非阻塞模型容许处理和 I/O 操做重叠进行,可是这须要应用程序根据重现的规则来检查 I/O 操做的状态。这样就剩下异步非阻塞 I/O 了,它容许处理和 I/O 操做重叠进行,包括 I/O 操做完成的通知。

除了须要阻塞以外,select 函数所提供的功能(异步阻塞 I/O)与 AIO 相似。不过,它是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞。

 

Linux 上的 AIO 简介

本节将探索 Linux 的异步 I/O 模型,从而帮助咱们理解如何在应用程序中使用这种技术。

在传统的 I/O 模型中,有一个使用唯一句柄标识的 I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这对等同于文件、管道、套接字等等)。在阻塞 I/O 中,咱们发起了一次传输操做,当传输操做完成或发生错误时,系统调用就会返回。

Linux 上的 AIO

AIO 在 2.5 版本的内核中首次出现,如今已是 2.6 版本的产品内核的一个标准特性了。

在异步非阻塞 I/O 中,咱们能够同时发起多个传输操做。这须要每一个传输操做都有唯一的上下文,这样咱们才能在它们完成时区分究竟是哪一个传输操做完成了。在 AIO 中,这是一个 aiocb(AIO I/O Control Block)结构。这个结构包含了有关传输的全部信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb 结构就被用来唯一标识所完成的 I/O 操做。这个 API 的展现显示了如何使用它。

1.内存优化
2.运维接口优化
3.日志管理优化
4.解析接口优化。
5.单表、单表+and、单表加多个and,多表
6.一致性Hash
7.跨库读
8.跨库写
9.后端拆链
10.生成SQL
11.数据字典

12.数据库检测

13.QOS