• 欢迎来到我的博客
  • [email protected]

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

学习笔记 tianlan 1年前 (2020-04-07) 1229次浏览 0个评论 扫描二维码
文章目录[隐藏]

参考

https://blog.csdn.net/baixiaoshi/article/details/48708347

https://www.cnblogs.com/thrillerz/p/7137682.html

https://www.cnblogs.com/walblog/articles/9066563.html

https://blog.csdn.net/IT_10/article/details/95387481

https://www.jianshu.com/p/578cf2e4892e

前提基础

IO 操作是由内核负责的,用户线程只是负责对 IO 操作结果进行加工处理。

IO多路复用

IO复用:多个IO可以复用一个用户线程。

IO多路复用:多路网络连接复用一个IO线程(用户线程)。

同步/异步、阻塞/非阻塞

同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

同步阻塞

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

同步非阻塞

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

解读:①同步:需要轮询内核IO操作后才能继续执行;

②非阻塞:read请求1后,立马返回到用户线程

即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。

异步非阻塞IO(操作系统提供API)

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

FD

linux下,所有的操作都是对文件进行操作,而对文件的操作是利用文件描述符(file descriptor)来实现的。每个文件进程控制块中都有一份文件描述符表(可以把它看成是一个数组,里面的元素是指向file结构体指针类型),这个数组的下标就是文件描述符。在源代码中,一般用fd作为文件描述符的标识。

最初级的IO复用

非阻塞的模式,当一个连接过来时,我们不阻塞住,这样一个进程可以同时处理多个连接了。

比如一个进程接受了10000个连接,这个进程每次从头到尾的问一遍这10000个连接:“有I/O事件没?有的话就交给我处理,没有的话我一会再来问一遍。
然后进程就一直从头到尾问这10000个连接,如果这1000个连接都没有I/O事件,就会造成CPU的空转,并且效率也很低,不好不好。

升级版的IO复用

上面虽然实现了基础版的I/O复用,但是效率太低了。于是伟大的程序猿们日思夜想的去解决这个问题…终于!

我们能不能引入一个代理,这个代理可以同时观察许多I/O流事件呢?

当没有I/O事件的时候,用户进程处于阻塞状态;当有I/O事件的时候,这个代理就去通知进程醒来?

于是,早期的程序猿们发明了两个代理—select、poll。

select、poll代理的原理是这样的:

当连接有I/O流事件产生的时候,就会去唤醒进程去处理。

但是进程并不知道是哪个连接产生的I/O流事件,于是进程就挨个去问:“请问是你有事要处理吗?”……问了99999遍,哦,原来是第100000个进程有事要处理。那么,前面这99999次就白问了,白白浪费宝贵的CPU时间片了!痛哉,惜哉…

注:select与poll原理是一样的,只不过select只能观察1024个连接,poll可以观察无限个连接。

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

上面看了,select、poll因为不知道哪个连接有I/O流事件要处理,性能也挺不好的。

那么,如果发明一个代理,每次能够知道哪个连接有了I/O流事件,不就可以避免无意义的空转了吗?

于是,超级无敌、闪闪发光的epoll被伟大的程序员发明出来了。

epoll代理的原理是这样的:

当连接有I/O流事件产生的时候,epoll就会去告诉进程哪个连接有I/O流事件产生,然后进程就去处理这个连接。

如此,多高效!

swoole高并发原理

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

下图类比本文之前讲过的东西:客户端连接 —— Reactor(“用户线程”) —— work、taskworker(“内核”)

IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程

当请求到达时,swoole是这样处理的:

请求到达 Main Reactor
        |
        |
Main Reactor根据Reactor的情况,将请求注册给对应的Reactor
(每个Reactor都有epoll。用来监听客户端的变化)
        |
        |
客户端有变化时(或者说Input完毕后),交给worker来处理
        |
        |
worker处理完毕,通过进程间通信(比如管道、共享内存、消息队列)发给对应的reactor。
        |
        |
reactor将响应结果发给相应的连接
        |
        |
    请求处理完成

因为reactor基于epoll,所以每个reactor可以处理无数个连接请求。 如此,swoole就轻松的处理了高并发。

 

swoole如何实现异步I/O

基于上面的Swoole结构图,我们看到swoole的worker进程有2种类型:
一种是 普通的worker进程,一种是 task worker进程。

worker进程是用来处理普通的耗时不是太长的请求;task worker进程用来处理耗时较长的请求,比如数据库的I/O操作。

我们以异步Mysql举例:

耗时较长的Mysql查询进入worker
            |
            |
worker通过管道将这个请求交给taskworker来处理
            |
            |
worker再去处理其他请求
            |
            |
task worker处理完毕后,处理结果通过管道返回给worker
            |
            |
worker 将结果返回给reactor
            |
            |
reactor将结果返回给请求方

传统的PHP-FPM处理请求(同步阻塞)

PHP的一个worker只能同时处理一个请求

(敲黑板 重点 面试会问到):master只是负责监听管理工作,并不是很多人认为的把客户端发来的请求分给worker进程处理,而是由worker进程负责客户端的请求监听和处理。(kill master后,worker仍然可以处理请求)

这里提一下,Nginx也类似,master进程并不处理请求,而是worker进程直接处理, 不过区别在于Nginx的worker进程是epoll异步处理请求,而PHP-FPM仍然是poll.

www.example.com
        |
        |
      Nginx
        |
        |
路由到www.example.com/index.php
        |
        |
加载nginx的fast-cgi模块
        |
        |
fast-cgi监听127.0.0.1:9000地址
        |
        |
www.example.com/index.php请求到达127.0.0.1:9000
        |
        |
php-fpm 监听127.0.0.1:9000
        |
        |
php-fpm 接收到请求
        |
        |
php-fpm 分发请求给worker
        |
        |
php-fpm 处理完请求,返回给nginx
        |
        |
nginx将结果通过http返回给浏览器

 

传统PHP不支持多线程的原因

worker进程不支持多线程,是单线程的


天蓝, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:IO多路复用、同步/异步、阻塞/非阻塞、swoole流程、php-fpm流程
喜欢 (0)
[[email protected]]
分享 (0)

您必须 登录 才能发表评论!