第2章 高并发IO的底层原理
2.1 IO读写的基本原理
为了避免用户进程直接操作内核,保证内核安全,操作系统将内存(虚拟内存)划分为两部分:一部分是内核空间(Kernel-Space)
,另一部分是用户空间(User-Space)
上层应用通过操作系统的
read
系统调用把数据从内核缓冲区
复制到应用程序的进程缓冲区
,通过操作系统的write
系统调用把数据从应用程序的进程缓冲区
复制到操作系统的内核缓冲区
。read和write两大系统调用都不负责数据在
内核缓冲区
和物理设备
(如磁盘、网卡等)之间的交换。这个底层的读写交换操作是由操作系统内核(Kernel)
来完成的。在Linux系统中,操作系统内核只有一个内核缓冲区。每个用户程序(进程)都有自己独立的缓冲区,叫作
用户缓冲区
或者进程缓冲区
。
Java客户端和服务端之间完成一次socket请求和响应(包括read和write)的数据交换,其完整的流程如下:
- 客户端发送请求:Java客户端程序通过write系统调用将数据复制到内核缓冲区,Linux将内核缓冲区的请求数据通过客户端机器的网卡发送出去。
- 服务端获取请求:在服务端,这份请求数据会从接收网卡中读取到服务端机器的内核缓冲区。Java服务端程序通过read系统调用从Linux内核缓冲区读取数据,再送入Java进程缓冲区。
- 服务端业务处理:Java服务器在自己的用户空间中完成客户端的请求所对应的业务处理。
- 服务端返回数据:Java服务器完成处理后,构建好的响应数据将从用户缓冲区写入内核缓冲区,这里用到的是write系统调用。服务端Linux系统将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议将数据发送给目标客户端。
2.2 四种主要的IO模型
阻塞IO(Blocking IO)指的是需要内核IO操作彻底完成后才返回到用户空间执行用户程序的操作指令。
阻塞 指的是用户程序(发起IO请求的进程或者线程)的执行状态。
非阻塞IO(Non-Blocking IO,NIO)指的是用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间去执行后续的指令,即发起IO请求的用户进程(或者线程)处于非阻塞状态,与此同时,内核会立即返回给用户一个IO状态值。阻塞和非阻塞的区别是什么呢?
阻塞是指用户进程(或者线程)一直在等待,而不能做别的事情;
非阻塞是指用户进程(或者线程)获得内核返回的状态值就返回自己的空间,可以去做别的事情。在Java中,非阻塞IO的socket被设置为NONBLOCK模式
同步IO是指用户空间(进程或者线程)是主动发起IO请求的一方,系统内核是被动接收方。
异步IO(Asynchronous IO,AIO)则反过来,系统内核是主动发起IO请求的一方,用户空间是被动接收方。
1、 同步阻塞IO
指的是用户空间(或者线程)主动发起,需要等待内核IO操作彻底完成后才返回到用户空间的IO操作。
在IO操作过程中,发起IO请求的用户进程(或者线程)处于阻塞状态。
2、同步非阻塞IO
指的是用户进程主动发起,不需要等待内核IO操作彻底完成就能立即返回用户空间的IO操作。
在IO操作过程中,发起IO请求的用户进程(或者线程)处于非阻塞状态。
- 同步非阻塞IO的特点是应用程序的线程需要不断地进行IO系统调用,轮询数据是否已经准备好,如果没有准备好就继续轮询,直到完成IO系统调用为止。这将占用大量的CPU时间,效率低下。
- 在高并发应用场景中,同步非阻塞IO是性能很低的,也是基本不可用的。但是此模型还是有价值的,其作用在于其他IO模型中可以使用非阻塞IO模型作为基础,以实现其高性能。
2、IO多路复用
一个用户进程(或者线程)可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核就能够将文件描述符的就绪状态返回给用户进程(或者线程),用户空间可以根据文件描述符的就绪状态进行相应的IO系统调用。
4、异步IO模型
在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕并放在了用户缓冲区内,内核在IO完成后通知用户线程直接使用即可。
异步IO类似于Java中典型的回调模式,用户进程(或者线程)向内核空间注册了各种IO事件的回调函数,由内核去主动调用。