web服务器

1、系统IO模型

1.1、消息通信机制

1、同步和异步
  同步:进程发出请求调用后,进程会一直处于等待状态,直到内核返回响应消息以后才能进行下一步操作,如果内核一直不返回数据,那么进程就会一直等待内核响应消息。
  异步:进程发出请求调用后,进程无需等待内核返回响应消息,会进行其他的操作或者发送下一个调用请求。
  阻塞:IO操作需要彻底完成后才返回到用户空间,调用结果返回之前,调用者被挂起,无法进行其他操作,调用者只有在得到结果之后才会返回,该线程在此过程中不能进行其他处理。
  非阻塞:IO操作被调用后立即返回给用户一个状态,无需等到IO操作彻底完成,最终的调用结果返回之前,调用者不会被挂起,可以去做别的事情。

1.2、同步阻塞型IO

  阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作师被阻塞,用户线程通过系统调用read发起IO操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据cp到用户空间,完成read操作,用户空间需要等待read将数据读取到buffer后,才继续处理接受的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。
  优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用CPU资源
  缺点:每个连接需要独立的进程/线程单独处理,这样虽然可以有效的利用CPU资源,但是代价就是多进程的大量内存开销。
  同步阻塞:程序向内核发送IO请求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回,则进程将一直等待并不再接收新的请求,并由进程轮询查看IO是否完成,完成后进程将IO结果返回给Client,在IO没有返回期间进程不能接收其他用户的请求,而且只有进程自己去查看IO是否完成。

 1.3、同步非阻塞IO

  在同步阻塞IO中,进程实际等待的时间可能包含两部分,一个是等待数据的就绪,另一个是等待数据的复制;与此不同的是,同步非阻塞IO的调用不会等待数据的就绪,如果数据不可读或者不可写,它会立即告诉进程。相比于阻塞IO,非阻塞IO结合反复轮询来尝试数据是否就绪,防止进程被阻塞,最大的好处便在于可以在一个进程里同时处理多个IO操作。由于需要进程执行多次的轮询来查看数据是否就绪,这样花费了大量的CPU时间,使得进程处于忙碌等待状态。

 1.4、多路IO复用

  在实际应用中,特别是在web服务器,同时处理大量的文件描述符是必不可少的(高并发),这时使用同步非阻塞IO显然不是合理的;因为服务器想要同时接受多个TCP连接的数据,就必须轮流对每个socket调用接收数据的 方法。不管这些socket有没有可以接收的数据,都要询问一遍,那么假如大部分socket并没有数据可以接收,那么就会浪费很多CPU时间用于检查这些socket。多路IO复用就绪通知,就提供了对大量文件描述符就绪检查的高性能方案,它允许进程通过一种方法同时监听所有的文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些描述符进行数据访问。

  select模型:select模型通过一个select()系统调用来监视包含多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行读写操作。select的一个缺点是单个进程能够监听的文件描述符的数量存在最大限制(Linux上一般为1024),所以加入使用select的服务器最多只能同时监听1024(64位操作系统默认为2048)个连接。由于nginx的prefork模型其底层所使用的的是select模型,因此nginx的prefork的最大连接数为1024。其次,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长;由于网络响应时间的延迟使得大量的TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以在一定程度上浪费CPU时间。