以下分析了php和node如何处理一个以及多个Http请求以及代码中的IO操作处理机制。
PHP的实现:
基本流程:
web服务器接受到请求之后,转发给一个php解释器进程,进程执行完代码并将输出结果返回给web服务器,web服务器返回给发起http请求的客户端。
多个请求时:
php-fpm : 主进程接受请求分配给worker进程处理,如果当前的worker进程都在执行中,则适当的开辟新的进程的处理。但是同一时刻,每个进程只能处理一个请求,当多个请求同时到达时,则必须开辟多个进程才能处理,否则只能挨个排队等待。由于不断开辟进程十分消耗系统资源,也很容易到达上限,所以单机情况下这种模式很难抗住大量的并发。这种多进程模型的优点是:当某个进程发生意外过执行时间过长时,不会阻塞住整个请求处理系统。
代码中出现IO调用时,如请求数据库或者请一个远程文件,php会一直等待请求结果或者超时,代码才能接着向下运行,当然,借助一些扩展,也可以实现非阻塞的事件回调机制。
Node的实现:
Node自己实现了Web服务器的功能,监听服务器端口等待连接,可以接受很多请求,就像nginx那样。当前连接成功并分析完这个请求后,接下来就是要处理该做什么了。假设每次请求要做一些数据的计算,同时接听到100次请求,那这100次运算在时间线上是排队处理的,因为只有一个node主进程在做这件事。但是请求中以IO操作为主时,比如每次请求的任务是到某个网页爬取这个网页的所有图片,那这100个请求会很快全部发出抓取远程图片的指令。
如果是阻塞式IO,则当前进程内一个请求拿到结果后才能发起下一个请求。Node的IO操作是异步的,IO操作没有拿到结果的时候不会阻塞住当前线程,所以可以发起很多IO请求。如果把IO操作当做攻城的话,就像战场上的统帅,同时向多个将军发出攻城的指令,然后将军们各自到自己的路线攻城就可以了。如何有哪路军队作战完成,就会汇报给将军,然后执行异步的处理。 (阻塞式:向1路军发送攻打A城的指令,1路军打完了,再向2路军发送攻打B城的指令...)
Node通过其事件引擎libuv实现了线程池来完成真正的异步IO操作。(不要和多路复用IO的概念混淆,epoll本质并非异步)