Code前端首页关于Code前端联系我们

BIO、NIO、AIO 哪个? PHP 可以做到这一点吗?

terry 2年前 (2023-09-25) 阅读数 47 #后端开发

由于 NIO、AIO、Netty 和 Promise 等话题很热门,我也想以 phper 的身份参与其中,结果发现我身边有 javaer 和 jser。那么PHP可以做NIOAIO吗?

什么 BIO、NIO、AIO

BIO 同步阻塞 I/O。

有朋友又问什么是同步阻塞

同步/异步阻塞/非阻塞

同步: 两个同步任务相互依赖,并且一个任务的执行方式必须依赖于另一任务。例如,在事件模型 A->B 中,您必须先完成 A,然后才能完成 B。换句话说,直到同步调用的被调用者处理完请求并且调用者等待返回结果后,调用才会返回。

异步:两个异步任务完全独立,一个任务的执行不必等待另一个任务的执行。换句话说,异步调用一调用就返回结果。您不必等待结果返回。当结果返回后,使用回调函数或者其他方法保存结果,然后做相关的事情。

阻塞:阻塞是发起请求,调用者一直在等待请求结果返回,即当前线程被挂起,无法参与其他任务。只有条件具备了,才能进行。

非阻塞:非阻塞是指发起请求。调用者不必等待结果返回,可以先做其他事情。

以上就是这四个字的解释。我们放到电脑IO上来更接地气的解释一下

BIO(Blocking I/O)

我们以快递为例。一家快递公司。部分工作是收集包裹。工作方式为一件一件取件。如果要发急单,就得排队,一件一件地来。这是同步。终于轮到你了。你把快递扔给他,他让你等一下。快递工作人员说:我们还有一些数据需要稍后录入,需要对快递人员进行检查。必须等到我们的快递公司检查完毕。只有这样你才能离开。这称为阻塞

NIO(无阻塞I/O)

同步非阻塞I/O

继续,以快递公司为例。这家快递公司注意到,有一些用户排在队伍的最后面,排队等候时间过长后,就去了隔壁的快递公司。他们应该做什么?快递公司想出了解决办法,购买了一批托运人和一批储物箱。当顾客到达时,快递员会被放入储物箱中,并向用户提供一个号码。当顾客退货时,无论之前的快递是否经过检查,他仍然会收到一个储物箱和一个号码。不同顾客之间不需排队,一到就被接受。这是非阻塞。我们来看看内饰。快递还是一一录入信息,进行X光检查,做到了同步。需要等待快递人员完成检查并拨打电话,顾客收到签收后才能离开快递点。

AIO(异步I/O)

异步非阻塞IO

一些爪哇人也称其为NIO2。快递公司对包裹收集进行了升级,并建立了快递储物柜。客户再次有邮寄需求,可以在到达时发布。进入快速储物柜,然后扫描手机上的二维码即可追踪储物柜的状态。顾客可以离开。至此,服务已被接受,客户可以立即离开。这是非阻塞。当快递员来取包裹时,他们一起将包裹从柜子里取出。快递点共同处理这些快递包裹。当他们遇到有问题的包裹时,他们不会立即停止手中的工作等待顾客出来,而是将其收起来。通知客户过来,然后继续处理下一个快递,就是异步

异步阻塞IO

同步/异步阻塞/非阻塞,这四个名词,两组一组,另一个是异步/阻塞

先举个例子。就是这个快递点。一群客户前来寄口罩到国外。由于很有可能不通过检查,所以快递点扣留了所有人。检查完所有货物后,将向每个人发送收据。这是。快递人员检查货件时发现,问题并没有立即报告给客户解决,而是被搁置一边继续处理下一件。这是异步

伪异步IO

这种模式下,底层实现是多个同步阻塞的BIO,同时运行。

最后总结一下:

阻塞和非阻塞是指I/O操作是立即返回还是在无法进行读写时阻塞(网卡满时写/网卡满时读)满的)。空的);同步异步是指当数据ready时,无论读写是同步还是异步,只是阶段不同。

区别

计算机中异步/同步的区别

以上是一些例子,只是为了帮助大家理解内存。接下来我们看一下计算机实现。

最初由计算机提供的网络服务使用CGI协议,纯BIO模型。cgi 进程侦听端口,并且在处理请求之前无法接收下一个 http 请求。这就是同步

实际的客户体验是“异步”的,即因为是后来优化的,CGI程序可以自行fork进程,达到响应多个http的效果 同时请求。

注意,我们这里讨论的基础是单进程,上面是异步/同步

电脑上屏蔽/解锁的区别

以购物流程为例。下订单时,用户必须执行以下操作:

  • 产品是否有售
  • 库存数量
  • 用户余额
  • 触发什么折扣规则
  • 彩票有效期
  • ..

一般的做法是一步步验证。上一个检查完成后,再进行下一个检查。这就是 阻止 的方式。

那么如何以非阻塞的方式做到这一点呢?假设在微服务环境中,产品、库存、优惠券、促销都是独立的系统。致电产品服务并提交请求以检查产品可用性;继续拨打电话,无需等待产品服务人员应答。库存服务,发起可销售商品库存请求;然后依次发送...检验请求,从而同时发起5个检验项目的请求。最后,我等待他们所有的请求都回复我,然后我们一起验证它们。所有检查均通过。这种请求被称为非阻塞,因为它不等待响应就继续做下一步事情。

转载自著名源码sifou

PHP能做什么

PHP与BIO的实现

PHP已经实现了,这是最基本的好吗?但在正常测试过程中,似乎并没有阻塞。好吧,我们一起做一个实验,尝试将nginx和php-fpm的进程限制为1个。php-fpm是一个多进程BIO。现在我们的力量已经变成了一个单一的过程。 ? 修改文件/etc/php/php-fpm/conf.d/www.conf

pm = static

pm.max_children = 1

pm.start_servers = 1

pm.min_spare_servers = 1

pm.max_spare_servers = 1

找到这些配置,将其更改为上面的值。

最终结果如下

什么BIO、NIO、AIO?PHP 能实现么?

我在index.php代码第一行添加了sleep。

<?php
sleep(5);

我们同时打开两个网页,尝试一起访问
什么BIO、NIO、AIO?PHP 能实现么?

通过火狐抓包可以发现,一个页面需要5秒,另一个页面需要9.3秒,( 0, 7 都是我的错)我手速慢) 这是BIO。

什么BIO、NIO、AIO?PHP 能实现么?

好吧,我们再做一个实验。将上述nginx和php-fpm配置中的1更改为2。然后我们打开三个网络页面,同时访问并尝试一下。

什么BIO、NIO、AIO?PHP 能实现么?

结果是,有两个网页,耗时5s,还有一个,耗时9s,也就是说服务器同时处理了两个请求,第三个请求请求等待 4 秒才被处理。 。这就是多线程BIO,可以同时接收服务的客户端数量取决于员工数量

PHP 和 NIO 实现

我们编写的大多数 php-fpm 代码和第三方框架都是 block。 PHP还支持非阻塞IO编程。

这里其他博主也使用PHP原生代码实现NIO编程:PHP套接字编程回顾。

I/O 多路复用

在这个小演示中,PHP 实现了两个核心 NIO 函数:stream_set_blockingstream_select()
通过上面的源码,我发现原生的NIO实现还是比较繁琐,难以阅读。同时我只想问:这NIO只是为了实现一个socket服务器吗?我们看一下Netty的官网。打开 Netty 主页。它的自我描述如下

Netty 是一个 NIO 客户端服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序。它大大简化和精简了TCP、UDP套接字服务器等网络编程。

第一句话:Netty是一个NIO客户端服务框架,可以快速轻松地开发协程客户端。第二句:简化网络编程,例如创建TCP和UDP套接字服务。

好吧,重点是什么?第一句是最重要的一点:开发协程客户端!回到我们的业务,我们只是举了一个例子。从购物到下订单,有很多流程需要监控。按照一般的BIO,程序时序图如下:

什么BIO、NIO、AIO?PHP 能实现么?

从上面可以看出,三个检查是分别依次执行的。 。那么顾客的等待时间就大于库存检验时间加上产品检验时间加上促销检验时间。

假设库存、产品、促销是三个微服务,购物车服务使用NIO客户端与这三个微服务进行通信。会是什么效果:

什么BIO、NIO、AIO?PHP 能实现么?

这里发起检查请求时,我们是按顺序发起的,但是我们不会等到第一个服务返回检查结果才发起下一个检查请求。最后三个服务返回后,将组合结果返回给用户。那么这三个检查的耗时就等于一个服务的检查耗时(耗时最长的服务)。显着缩短购物车服务响应时间。 ? WebSocket

服务,TCP服务或Http服务。是的,这是最基本的,但是NIO框架的主要优势在于开发一个非阻塞的客户端!这就是优点,这就是BIO的编程差异所在。

NIO Client

看到上面两个时序图,我想向大家展示如何使用 PHP 自己的代码来实现它 A PHP-BIO 。 PHP Simple NIO Server

建议您点击链接克隆源代码 https://gitee.com/xupaul/php-nio-server在本地运行。通过查看截图会更容易理解。

什么BIO、NIO、AIO?PHP 能实现么?

这三个依赖服务的响应时间非常耗时。我将其设置为:库存:4s,产品:2s,促销:6s

蓝色框和黄色框标记两个请求。我们主要看参数noBlocking:true/false的区别,第一个是非阻塞请求,可以看到总共需要6s,第二个总共需要12s! (为什么第三次和第二次不一样——还剩6秒大家研究)。非阻塞IO的好处是显而易见的。不过这样的代码结构并不是那么友好。 nio_server.php代码中有两种请求方法。阻塞代码进程仍然可以理解检查并在检查完成后返回详细结果。非阻塞方式中,开始三项检查后,程序流程开始进入handleMessage。代码进入哪个分支取决于来自socket_read的消息。没有运行程序,没有文档,很难理解整个程序流程。

是否有任何有用的 php 库可以让我们的编码更加友好?这里介绍一下ReactPHP

这里我用ReactPHP重新实现了nio_server,代码在这里

。这个回调代码看起来有点像NodeJS。当你的PHP没有使用libev这样的扩展时,ReactPHP使用了内部循环stream_select(),可以看源码~/react/event-loop/ src/StreamSelectLoop.php@290 .

执行效果如下:
什么BIO、NIO、AIO?PHP 能实现么?

如果可以同时发起这个功能的请求,就提一下curl_multi,它包含多个 curl 可以同时发起 请求,并最终不断检查所有curl 请求是否已完成。这只是为了在发起多个 Http curl 请求阶段实现非阻塞操作。

还有一个扩展pThreads,可以实现多线程,但是对PHP编译参数有限制,必须以线程安全模式运行。

pThreads 不再是 PHP 官方推荐的扩展。自然,这种扩展属于伪异步IO

PHP和AIO

PHP异步非阻塞编码。

非阻塞I/O系统调用(non-blocking system call)和异步I/O系统调用(asynchronous system call)的区别是:

  • 非阻塞I/ O 系统 调用read()立即返回所有可以立即获取的数据,可以是完整的结果,可以是不完整的结果,也可以是空值。
  • 异步 I/O 系统调用 read() 结果必须完整,但此操作完成的通知可以推迟到将来的某个时间。
<?php

/**
 * 消息处理
 */
function handleMessage() {
    global $changed, $clients, $cartCheck;
    foreach ($changed as $key => $client) {
        while (true) {
            // read socket data
            $msg = @fread($client, 1024);
//            $msg = 1;
            if ($msg) {
                // application process
            } else {
                if (feof($client)) {
                    // TODO check data eof
                }
                break;
            }

可以看到在文件~/nio_server.php中,虽然stream_set_blocking设置为false,但是在第209行fread(),这是它是循环读取的,这是阻塞读取。该系统功能的响应能力受系统IO影响。

在异步调用中,当I/O事件发生时,系统将数据复制到用户内存,即准备好数据,然后通知用户程序。

那么原生PHP显然是不支持的。这里我们需要引入PHP扩展,即EventEv扩展。本博客主要是关于事件

Event扩展基于libevent库进行封装,Ev扩展基于libev 封装出库。通过PHP接口和C库接口可以看到两者之间的联系。因此,如果通过PHP文档找不到相关信息,可以查看C库文档。而且Libevent正在衰落,不推荐大家使用。

这是Tcp服务器演示

使用事件来执行此操作在演示使用i当涉及到事件缓冲区 ,读取和写入都与 Buffer 交互。 Buffer数据是用户态数据,不会等待系统I/O或被阻塞,从而避免了程序的需要。时间花在复制 I/O 数据上。由此看来,PHP还可以实现程序AIO来提高CPU使用率。

说到这里,你可能会觉得这个PHP的AIO有点牵强。我找到了其他博主的论点来帮助大家理解。这两张图显示了用户程序和内核的接受情况。 阻塞异步交互之间的异同。

什么BIO、NIO、AIO?PHP 能实现么?

上面是非阻塞IO,下面是异步IO。中间的区别在于,非阻塞IO的应用必须不断访问内核获取数据(显然任何访问都是有响应的,都可以获得数据),但也不一定这样才有可能完成它;而异步IO的特点是你告诉内核去获取数据,当它完成时,我把它一起发送给应用程序。这是Linux对异步IO的定义。

什么BIO、NIO、AIO?PHP 能实现么?

那么看看我们的演示。这是一个简单的 TCP 服务器。 TCP请求系统可以知道数据包的大小以及数据是否已被接收。这就是传输层的作用。在我们的应用层面,接收到数据后,我们需要对数据进行合并、外包、转码。这与AIO不同。数据结果一定是完整的,概率略有不同(当然在系统层面是完整的)。在应用层面,一次性收到的不一定是完整的数据。 ,那么就必须额外编写代码来解决共封装、外包、浸胶的问题。这就是需要AIO来实现Tcp Server的问题。

要解决上述问题,需要调整TCP通信协议。相当于自己开发了RPC框架。

我们来看看Http。应用层有清晰开放的协议(协议有头无尾,表示每个请求的具体长度),有丰富的实现。这是一个很棒的编程协议,可与 AIO 一起使用。而 PHP 的 Event 扩展是由 EventHttp 实现的。

话不多说,让我们开始演示

<?php
...

/**
 * event http 请求回调函数
 * 
 * @param   \EventHttpRequest   $req    Http请求对象
 */
function _http_about($req) {
    echo __METHOD__, PHP_EOL;
    // print request URL
    echo "URI: ", $req->getUri(), PHP_EOL;
    // print request's headers
    echo "Input headers:"; var_dump($req->getInputHeaders());
    echo "\n >> Sending reply ...";
    /**
     * @var \EventBuffer    $buf
     */
    $buf = $req->getOutputBuffer();
    $buf->add("It's about Event http server");
    $req->sendReply(200, "OK", $buf);
    echo "OK\n";
}

这是一个回调函数,输入参数是EventHttp封装的http请求对象。这样就满足了上面调用时不阻塞的要求。数据完全准备好后,报告回调 - 异步 I/O。好了,借助enEvent PHP 实现了 Aio.

好好好好好好好好好好好好好好好Nio AIO的可行性。我还给大家展示了几个Demo,简单展示了如何编写异步非阻塞程序。显然异步编程对大家的要求是比较高的。当需要启动IO操作时,必须以非阻塞的方式调用,否则整个进程将被阻塞。纯异步编程是单个进程。阻塞后,服务无法响应新的请求。同时,我们常用的必须使用的PDO、mysqli和Redis等扩展也只提供了阻塞读接口。在当今的 PHP 环境中,可以说“几乎所有”第三方框架都块编码。如果您的项目使用其他框架,您编写的代码就可以了。无法保证您所依赖的外部框架会阻止请求。 I\O。所以一般异步PHP编程都会使用多进程异步,这样可以通过异步来提高每个请求的响应速度。如果进程被阻塞,则允许其他空闲进程处理新传入的请求。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门