系统IO模型
什么是IO
我们在学计算机基础的时候,一定学习过计算机由哪五部分组成,那就是控制器、运算器、存储器、输入设备、输出设备。而计算机再与输入设备打交道的时候就是input,也就是I,跟输出设备打交道的时候就是output,也就是O。那么我问你,把一个字符串写入一个文件,这是input,还是output呢?
首先,文件存储在磁盘里,那么磁盘是存储设备,输入设备,还是输出设备呢?事实上,磁盘既是存储设备又是输入输出设备。针对把一个字符串写入文件这个场景来说,它承担了输出设备和存储设备的功能。具体是input还是output,可以理解为数据相对于我们运行程序的流向。数据从其他设备流向程序,那么就是input,数据通过程序流向其他设备,就是output。网卡的情况和硬盘很像,同样既是输入设备又是输出设备。
那么IO模型,就是系统实现的从输入设备读取数据以及将数据写入输出设备的模型。
系统有哪些IO模型
目前一般系统有四种IO模型,分别为同步阻塞IO、同步非阻塞IO、IO多路复用、异步IO。在讲这些IO模型具体的原理之前,先来了解一下,应用程序与输入输出设备交互的全过程。
应用程序是运行在操作系统之上的,应用程序没有直接与硬件交互的权利,所有与硬件打交道的事情,都要委托操作系统完成。应用程序在委托操作系统读数据的时候,用到的是系统调用read,在委托操作系统写数据的时候,是调用的系统调用write。所以在发起读取输入设备内容的时候,要经历①②两个步骤,内容才能到达用户空间。同样的,在写数据到输出设备的时候,也需要经历③④两个步骤。
刚刚提到的同步、阻塞的概念就是针对这几个步骤来说的。
同步阻塞IO
就是应用程序开始发起读操作的时候,就会进入阻塞状态,等待内核数据从输入设备复制到内核缓冲区,还要等待数据从内核缓冲区复制到用户缓冲区。
同步非阻塞IO
就是应用程序开始发起读操作,这个时候如果数据还没有完全复制到内核缓冲区,那么直接给应用程序返回错误,不需要应用程序阻塞等待。应用程序可以选择去做其他事情,或者轮训请求。直到调用读操作的时候返回成功,这个时候程序进入阻塞状态,等待数据从内核缓冲区复制到用户缓冲区。
IO多路复用
这种IO模型实际上与同步非阻塞IO本质上是一样的,只是在轮训的方式上做了优化,在同步非阻塞IO的模型中,需要应用程序不断的尝试读数据,这会给应用程序造成很大压力,因为每个连接都需要应用程序自己管理,轮训状态,如果无限循环,事实上跟同步阻塞IO模型区别也不大。为了提高效率,开始引入了一个新的系统调用,就是select,或者poll或者epoll,他们的作用是一样的,只是原理略有差别,执行效率不同。他们的作用就是让应用程序在每次循环检查连接状态的时候,可以一次检查成千上万个连接的状态,只要有连接状态可用,那么就交给应用程序,应用程序再根据具体的状态做读写处理。但是在读写的时候,数据在内核缓冲区和用户缓冲区之间复制的过程,依然是阻塞的。
异步IO
为了进一步优化效率,异步IO就应运而生,异步IO就是用户发起读写操作,当整个数据的两个步骤完成以后,系统才通知用户处理完成,用户收到通知的时候,如果是读操作,这个时候数据已经到了用户空间,不需要等得数据从内核空间复制到用户空间的时间。
目前用的最多的还是第一种同步阻塞IO和第三种IO多路复用。第一种模型简单,代码实现也简单,在Java中,默认的socket就是同步阻塞IO,Java中的NIO,就是IO多路复用模型,NIO中的selecter就是系统中select或者epoll系统调用的映射。在利用Netty做高并发的网络开发的时候,也是IO多路复用的用的最多。
系统配置
在了解了IO多路复用模型适合高并发场景以后,直接开发程序实时上还不能发挥出它的真正实力,这是因为在操作系统中一般限制了单个进程能够打开的最大句柄数量,一个句柄就代表着一个socket链接。在Linux环境中可以是用如下命令来查看限制:
ulimit -n
一般linux默认是1024个句柄限制,这个在作为高并发服务器的系统中是远远不够的,如果句柄限制超过最大显示会报如下错误
Socket/File:Can't open so many files
修改限制有多种方式
只修改当前会话
ulimit -n 1000000
永久修改
如果想永久修改,那么可以修改系统的开机启动脚本,例如/etc/rc.local
添加如下命令:
ulimit -SHn 1000000 #-S表示软性极限值,-H表示硬性极限值
终极方法
修改配置文件/etc/security/limits.conf
,增加如下配置
soft nofile 1000000
hard nofile 1000000