I/O模型之Web应用服务
文章目录
前言
当前最为流行的Web服务器就属Httpd和Nginx。Web 服务器到底干了什么事?简单点说就是接受用户请求,响应用户请求。这个过程的实现就是完成了跨主机之间的通信,而主机间的通信我们使用的是BSD(套接字)创建的Socket。
一、Socket通信模型:
为了能够受到客户端的请求,客户端使用一个Socket监听在某个IP+Port之上。接受到请求,将客户端所要的资源准备好后,在响应给客户端。如果此时受到的请求不是一个,而是多个的话,服务器端要如何完成响应?如果一个一个的响应,显然太慢了。如何同时完成多个请求(并发响应)?接下来我们就是谈谈各种I/O模型,以及Httpd和Nginx中所使用的I/O模型。
二、不同角度的I/O模型
我们都知道程序员编写程序是一定会调用函数的,而一旦程序员调用了函数,也就意味着程序从主函数的上下文就入了函数的上下文。这时候有两个问题,一个是调用者(此情况下是主函数)要不要等被调用函数返回,另一个是被调用函数(被调用者)如何告诉调用者自己此时的状态。I/O模型的不同,实际上就是程序员调用的函数不同。调用的函数确定了也就意味着,调用者和被调用者采取那种行为方式也确定了。接下来我们从不同的角度看I/O模型的分类。
1. 阻塞与非阻塞(调用者角度)
阻塞:当程序进入被调用者上下文时调用者会等被调用返回,调用者被挂起。程序执行流程图二左。
非阻塞:当程序进入被调用者上下文时,调用者也不会继续执行,调用者不会被挂起。而是不断的询问被调用者的状态。不断询问是需要消耗资源,实际上还没有阻塞性能高。程序执行流程下图二右。
2. 异步与同步(被调用者角度)
关注被调用函数如何将结果通知给调用者,看的是被调用者的消息通知机制
-
要有比较深入的理解同步和异步我们需要稍微详细的看一看一个完整的I/O过程,这有了解了I/O的整个过程,我们才知道从何处优化我们的I/O模型。
- 第一步用户空间通过系统调用进入内核,
- 第二步通过系统调用(函数)从磁盘中获取用户所需数据,
- 第三步将用户所需资源放入内核Buffer中,这样做的好处是当再此访问此数据是不需要从磁盘中获取,我们知道从磁盘中获取数据时最耗时间的。
- 第四步就是将内核中的数据拷到用户空间。
这整个I/O过程我们将其分为两阶段
- 第一阶段(Wait For Data):内核从磁盘获取资源放入Buffer中,也就是图中2,3步。
- 第二阶段(Copy Data):将数据从内核中拷贝到用户空间(这是我们实际谈到的I/O过程)。
在内核完成I/O时用户空间的进程在干嘛?如果用户空间进程在整个I/O过程中等待调用者返回,则此I/O模型称之为同步;如果用户空间进程向内核发起系统调用后,不是原地等待而是又去接受其他的请求,我们称之为异步,这也是我们希望看到的。
这有一个问题,用户空间进程不在原地等待他怎么知道内核完成了资源的获取?就像我们去餐馆点了一份面,需要二十分钟,我们就到隔壁网吧上网去了,面好了我们怎么知道?在异步I/O模型中,被调用者(餐馆老板,内核)会通过状态,通知或回调机制告知调用者(吃面的客户,用户进程)被调用者的运行状态(面好了没,进程所需资源好了没)。
3. 总结同步和异步的区别
同步:等待对方返回消息,即等到上面两个I/O完成。
异步:发起系统调用后,调用者马上又去响应其他的请求(开辟新的线程去响应别的请求),而被调用者通过状态、通知或回调机制通知调用者被调用者的运行状态。当得知资源准备好了,就构建响应报文响应客户。
三、复用型IO调用
一个线程注册多个I/O事件,每个I/O事件完成后都会通知调用者。此时用户空间进程没有阻塞在任何一个I/O上,而是阻塞在I/O复用器上,例如,select,poll函数。而这种IO复用的机制原先内核中是没有,BSD组织研发的库调用,调用函数为select,而且默认每个线程调用最多只能监听1024个I/O事件,这个值可在编译内核时进行修改,但大于1024性能没多大影响。CSV组织研发库调用,调用函数为poll,注册的I/O事件不受1024限制但过了1024性能也没变化。
四、Linux中五种I/O模型
1)阻塞I/O(blockingI/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll) (I/O multiplexing)
4)信号驱动I/O(epoll)(signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O(the POSIX aio_functions))
前四种都是同步,只有最后一种才是真正的异步IO。
在这介绍一下上面没有涉及的信号驱动模型,套接字使用信号驱动,它使用了一个信号处理函数,进程继续运行并不阻塞,当内核将数据放入内核缓冲区后,进程就会收到一个SIGIO信号,并在信号处理函数中调用I/O操作函数处理数据。他也是I/O复用中的一种,从上图中可以看出它在I/O的第一阶段实现了异步,由于第二阶段是阻塞的故依旧是阻塞的。
五、Http中的I/O模型
Http支持多处理模块MPM(MultipathProcessing Modules)
1. profork:多进程模型。
一个主进程只负责生成n个子进程,子进程才是真正的工作进程,每个进程响应一个请求。在Httpd服务启动后他就会生成对个空闲进程,为随时能够到达的请求服务。profork模型中主进程使用的使用的I/O复用中的Select机制,故子进程最多不超过1024个。我们知道管理一个子进程包括创建,需要分配内存资源,进程进入僵死态后还需要等父进程销毁。这对父进程来说是很大的开销。
2. worker:多进程模型。
二级模型,父进程生成子进程,子进程生成线程,每个线程响应一个请求。在Linux中进程是轻量级线程,父进程在使用fork函数通过拷贝自身创建子进程,子进程除了一些ID之类的标识信息不同外,所有的资源与父进程共用,只有子进程在更改资源时才会复制到自身结构体中,也就是写时复制(Copy On Write)。故profork模型和worker模型区别不大。
3. event模型:事件驱动模型。
二级结构,主进程生成多个子进程,每个子进程基于事件驱动模型可相应多个请求。事件驱动模型在httpd-2.2版本中为测试模型,在httpd-2.4中才可用于生产。
六、Nginx中的I/O模型
1. event模型:
nginx创建的初衷就是解决C10K问题(2005,2006年,现在是C10K,C100k问题),也就是并发达到一万是Http就难以承载,我们急需一种高并发,轻量级web服务器。而nginx就是在这种情况下产生的。而它的高并发采用event模型,调用的函数是epoll。换句话说,基于epoll的事件驱动模型是nginx高并发的优势所在,作为web服务器的nginx一定会使用epoll模型,不然失去了nginx作为web服务器使用的意义。
2. select模型
3. poll模型
4. epoll模型
(注:参考 https://blog.csdn.net/lirou_/article/details/69382520,感谢博主的总结归纳和分享)