溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

發(fā)布時間:2021-09-07 10:09:32 來源:億速云 閱讀:99 作者:小新 欄目:web開發(fā)

這篇文章主要為大家展示了“NIO、BIO、AIO與PHP實現(xiàn)的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學習一下“NIO、BIO、AIO與PHP實現(xiàn)的示例分析”這篇文章吧。

什么BIO、NIO、AIO

BIO 同步阻塞I/O。

有小伙伴又要問了啥叫 同步,啥叫阻塞?。?/p>

同步/異步 阻塞/非阻塞

同步: 兩個同步任務相互依賴,并且一個任務必須以依賴于另一任務的某種方式執(zhí)行。 比如在A->B事件模型中,你需要先完成 A 才能執(zhí)行B。 再換句話說,同步調(diào)用種被調(diào)用者未處理完請求之前,調(diào)用不返回,調(diào)用者會一直等待結(jié)果的返回。

異步: 兩個異步的任務完全獨立的,一方的執(zhí)行不需要等待另外一方的執(zhí)行。再換句話說,異步調(diào)用種一調(diào)用就返回結(jié)果不需要等待結(jié)果返回,當結(jié)果返回的時候通過回調(diào)函數(shù)或者其他方式拿著結(jié)果再做相關(guān)事情,

阻塞: 阻塞就是發(fā)起一個請求,調(diào)用者一直等待請求結(jié)果返回,也就是當前線程會被掛起,無法從事其他任務,只有當條件就緒才能繼續(xù)。

非阻塞: 非阻塞就是發(fā)起一個請求,調(diào)用者不用一直等著結(jié)果返回,可以先去干其他事情。

以上就是這四個詞匯的解釋,那么放到計算機IO上,比較接地氣的解釋

BIO (Blocking I/O)

那么我們拿快遞攬件來舉例,一個快遞公司,有一部分工作是攬件,它的工作模式是只能一個一個的攬件,你要寄快遞,必須排隊,一個一個的來,這就是 同步 。好不容易輪到你了,你把快遞一扔給他,他還讓給你等著,快遞工作人員說,我們這后面還有些信息要錄入,快遞要檢查,必須等我們快遞公司檢查完畢后,你才能離開,這叫 阻塞 。

NIO (No-Blocking I/O)

同步非阻塞的I/O

繼續(xù)啊,拿快遞公司舉例。這個快遞公司發(fā)現(xiàn)有些用戶在后面排隊,排著排著,太久了就去隔壁快遞公司了,怎么辦呢?快遞公司想了個辦法,置辦了一個發(fā)號器和一批收納盒。來一個客戶,就把快遞放在一個收納盒里,再給用戶一個編號,此時再來一個用戶,不論前面一個的快遞是否檢查完畢,還是給他一個收納盒,發(fā)一個編號。不同客戶之間不排隊,一來就被受理了,這就是 非阻塞。 我們再來看看內(nèi)部,快遞呢還是一個個地錄入信息,X光檢查,這樣就是 同步 運行的,等待快遞人員檢查完畢叫號,客戶拿到回執(zhí)才能離開快遞點。

AIO (Asynchronous I/O)

異步非阻塞IO

也有Javaer叫他 NIO2,快遞公司攬件又升級了,做了一個快遞柜,客戶又寄件需求,來了就放入快遞柜,然后通過手機掃碼關(guān)注這個柜子的動態(tài),客戶就可以離開了,此時服務被受理,并能馬上離開。這就是 非阻塞 。等到快遞人員來攬件時,會將柜子里面的寄件一并取走,快遞點集中一起處理這些快遞件,發(fā)現(xiàn)有問題的件,不是立即停下手中的活等待客戶來出來,而是放一旁通知客戶來,然后繼續(xù)處理下一個快遞,這就是 異步。

異步 阻塞 IO

同步/異步 阻塞/非阻塞,這4個名詞,兩兩組和,還有一個就是 異步/阻塞。

那么我們還是先把例子舉出來吧,還是這個快遞點,來了一批客戶來寄口罩到國外,由于有很大的可能會通不過檢查,所以,快遞點把大家都留了下來。等所有的 寄件 都檢查完了在統(tǒng)一給大家發(fā)送回執(zhí)單,這就是 阻塞 ??爝f人員檢查寄件時,發(fā)現(xiàn)問題不是立馬通知客戶來處理,而已放到一邊,繼續(xù)處理下一個。 這就是 異步。

偽異步 IO

這種模式,底層實現(xiàn)是多個 同步阻塞的BIO, 同時運行。

最后總結(jié)一下:

阻塞與非阻塞指的的是當不能進行讀寫(網(wǎng)卡滿時的寫/網(wǎng)卡空的時候的讀)的時候, I/ O操作立即返回還是阻塞;同步異步指的是,當數(shù)據(jù)已經(jīng) ready 的時候,讀寫操作是同步讀還是異步讀,階段不同而已。

區(qū)別

異步/同步在計算機區(qū)別

以上是一些舉例,只是幫助大家理解記憶,接下來我們看看計算上的實現(xiàn)。

最初計算機提供的Web服務,采用的是 CGI 協(xié)議,就是純正的 BIO 模式。一個cgi進程監(jiān)聽一個端口,處理完一個請求,才能接收下一個http請求。這就是同步。

而客戶的實際體驗式是"異步"的,那是因為后來優(yōu)化了,CGI 程序能夠自我fork進程的達到同時響應多個http請求的效果。

注意,我們這里討論的基礎(chǔ)是 單進程 ,上的 異步/同步。

阻塞/非阻塞在計算機區(qū)別

這里拿購物流程舉例,用戶的下單,需要做如下操作:

  •  商品可售否

  •  庫存數(shù)量

  •  用戶余額

  •  觸發(fā)哪些優(yōu)惠規(guī)則

  •  獎券有效性

  •  ...

按照一般做法就是一步步驗證,上一個檢查完了,再進行下一個檢查,這就是 阻塞 的方式。

那么非阻塞方式如何做呢,假設(shè)在微服務環(huán)境中,商品,庫存,獎券,促銷都是獨立的系統(tǒng),調(diào)用商品服務,發(fā)起商品可售檢查請求;不等商品服務回復,繼續(xù)調(diào)用庫存服務,發(fā)起商品可售庫存請求;緊接著依次發(fā)出...檢查請求,這樣5個檢查項目的請求同時發(fā)起,最后,我等他們所有的請求都回復我,再來一起來校驗是否所有的檢查都通過了。就這種發(fā)起請求不等響應,就繼續(xù)做下一件事的叫 非阻塞 。

PHP 能做什么

PHP 與 BIO 實現(xiàn)

PHP已經(jīng)實現(xiàn)啦,這是最基本的好么。但平時測試時卻感覺是不阻塞啊,好,我們來一起做個實驗,將nginx和php-fpm的進程限制為1個試試。php-fpm就是 多進程的 BIO,現(xiàn)在我們強項改成單進程。

  •  調(diào)整Nginx配置

調(diào)整 /etc/nginx/nginx.conf 文件:

## 把nginx worker數(shù)量設(shè)置為1  worker_processes 1;

好了之后我們通過ps命令檢查下

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

  •  調(diào)整PHP配置

調(diào)整 /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

找到這幾個配置都改為如上數(shù)值。

最后的結(jié)果如下

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

我在index.php代碼里面加入第一行就加入了sleep。

<?php  sleep(5);

我們同時打開兩個網(wǎng)頁,一起訪問試試

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

通過Firefox 抓包可以發(fā)現(xiàn),其中一個耗時5s,另一個頁面耗時9.3s,(0.7s誤差是我手速慢了) 這就是 BIO。

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

好的,我們再做一個實驗。把以上nginx,php-fpm配置中1改成2.然后我們打開三個網(wǎng)頁,同時訪問試試看。

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

結(jié)果是有兩個網(wǎng)頁耗時5s,一個是9s,也就是說服務器同時處理了2個請求,第三個請求等待了4s才被處理。這就是 多線程-BIO,一個服務同時接待的客戶數(shù)量取決與worker的數(shù)量。

PHP 與 NIO 實現(xiàn)

我們寫的大部分php-fpm代碼以及第三方框架都是阻塞的。PHP也是支持非阻塞IO編程的。

這里其他博主也用PHP原生代碼實現(xiàn)NIO編程: PHP回顧之socket編程。

I/O 多路復用

在這段小Demo中,PHP 實現(xiàn) NIO 核心兩個函數(shù)就是 stream_set_blocking、stream_select()。

通過以上源碼,發(fā)現(xiàn)原生的NIO實現(xiàn)還是比較繁瑣,不易讀的。同時,我就想問一句了,這個 NIO 就是為了實現(xiàn)一個 socket server 么,我們來看看Netty 官網(wǎng)。打開Netty首頁,它是這樣描述自己的

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

第一句話:Netty是一個 NIO 客戶端 服務框架, 能快速輕松地開發(fā)協(xié)程客戶端。第二句話:簡化了網(wǎng)絡(luò)編程,如創(chuàng)建TCP和UDP套接字服務。

好,重點是什么?第一句話就是重點&mdash;&mdash;開發(fā) 協(xié)程客戶端!回到我們業(yè)務上,剛剛舉了一個例子,購物到下單,有很多個流程需要做檢查,按照一般的BIO那么程序時序圖如下:

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

從上可以看到,三個檢查依次分開執(zhí)行。那么客戶的等待時間是大于,庫存檢查時間加上,產(chǎn)品檢查時間加上,促銷檢查時間 的。

假設(shè), 庫存,產(chǎn)品,促銷是三個微服務,然后購物車服務用 NIO客戶端,與這三個微服務交互,那么會是怎樣的效果呢:

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

這里,我們發(fā)起檢查請求時,是按照順序發(fā)起的,但不等第一個服務返回檢查結(jié)果就開始發(fā)起下一個檢查請求。最后三個服務都返回后,綜合結(jié)果,返回給用戶。那么這三個檢查的耗時,就等于一個服務(耗時最長的那個服務)的檢查耗時。大大減少得了購物車服務響應時間。

我看到一些 Netty、 NodeJS、Swoole 等教程 通篇都在講如何實現(xiàn)一個WebSocket服務,TCP服務或者是Http服務。對,這是最基礎(chǔ)的,但 NIO 框架核心優(yōu)勢在開發(fā)一個非阻塞客戶端!這才是它的優(yōu)勢,這才是和 BIO 編程差異化所在。

NIO 客戶端

看到以上兩個時序圖,還是給大家演示一下用PHP原生代碼實現(xiàn)一個 PHP-BIO 。 PHP Simple NIO Server

建議大家點擊鏈接,把源碼git clone https://gitee.com/xupaul/php-nio-server 到本地運行一下,再來看截圖更容易理解。

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

這三個所依賴的服務響應耗時,我設(shè)置為:inventory: 4s, product: 2s, promo:6s

藍色框和黃色框標注了兩個請求,我們主要看參數(shù) noBlocking: true/false 的不同, 第一個是非阻塞方式請求, 可以看到共耗時6s,第二個共耗時12s! (第三個為啥和第二個耗時不一樣&mdash;&mdash;6s這個留給大家去研究)。顯而易見得非阻塞IO的優(yōu)勢。不過這代碼結(jié)構(gòu)就不那么友好了,看到代碼 nio_server.php 中,有兩種請求方式,阻塞代碼流程還能看懂檢查完成后就綜合結(jié)果返回,而非阻塞方式中,發(fā)起三個檢查后程序流程就開始進入到handleMessage,代碼進入哪個分支,取決于 socket_read 的消息,不運行起程序來,沒有文檔,很難搞懂整個程序流程。

那么,有沒有什么什么方便的php類庫,讓我們編碼更友好一點呢,這里介紹下 ReactPHP

這里我用ReactPHP重新實現(xiàn) nio_server, 代碼在這里

這個回調(diào)代碼寫起來有點 NodeJS 的味道呢,當你的PHP沒啟用 libev 之類的拓展時,ReactPHP內(nèi)部Loop依然用的 stream_select(), 可以看源碼 ~/react/event-loop/src/StreamSelectLoop.php@290 .

執(zhí)行效果如下:

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

能同時發(fā)起請求這個功能,那還得提一下 curl_multi, 它能同時發(fā)起多個curl請求,最后不斷檢查是否所有的curl請求已完成。這只是在發(fā)起多個Http curl請求階段做到 非阻塞 運行。

還有個拓展pThreads,能夠?qū)崿F(xiàn)多線程,不過對PHP編譯參數(shù)有限制,需要在線程安全的模式下運行。

pThreads 現(xiàn)在已不是PHP官方所推薦使用的拓展了,當然了這種就屬于偽異步IO范疇了

PHP 與 AIO

PHP 異步&非阻塞 編碼。

此處, 非阻塞I/O 系統(tǒng)調(diào)用( nonblocking system call ) 和 異步I/O系統(tǒng)調(diào)用 (asychronous system call)的區(qū)別是:

  •  一個非阻塞I/O 系統(tǒng)調(diào)用 read() 操作立即返回的是任何可以立即拿到的數(shù)據(jù), 可以是完整的結(jié)果, 也可以是不完整的結(jié)果, 還可以是一個空值。

  •  而異步I/O系統(tǒng)調(diào)用 read() 結(jié)果必須是完整的, 但是這個操作完成的通知可以延遲到將來的一個時間點。 

<?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 中, 雖然設(shè)置了 stream_set_blocking false, 但是在209行的 fread() , 這是在一個循環(huán)里讀,這是一個阻塞讀取。這的系統(tǒng)函數(shù)的響應速度是受系統(tǒng)IO影響的。

而異步調(diào)用中,當有I/O事件時,系統(tǒng)會將數(shù)據(jù)復制到用戶內(nèi)存中,也就是準備好數(shù)據(jù),再通知到用戶程序。

那么原生PHP顯然是不支持的,這里呢就要引入PHP拓展,就是 Event,或者 Ev 拓展。這篇博客主要講 Event。

Event 拓展是基于 libevent 庫封裝而來,而 Ev 拓展是基于 libev 庫封裝而來。 通過PHP接口,和C庫的接口就能看到他們之間的聯(lián)系,所以,如果通過PHP文檔找不到相關(guān)資料可以去,看看C庫的文檔。而 Libevent 年久失修,不推薦大家使用。

這里放上用Event實現(xiàn)的Tcp Server demo

在用Event做這個demo中,我用到了EventBuffer ,讀、寫都和Buffer交互, Buffer數(shù)據(jù)是用戶態(tài)數(shù)據(jù),不會等待系統(tǒng)I/O或被阻塞,避免了程序耗時在I/O數(shù)據(jù)拷貝上。由此PHP 也能實現(xiàn) AIO 程式,提高CPU利用率。

講到這里,就會感覺這個PHP的AIO有些牽強了,我這找了其他博主的論點來幫助大家理解,這兩張圖展示了 用戶程序,與內(nèi)核采用 分阻塞 和 異步 交互時的異同。

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

上面是非阻塞IO,下面是異步IO。中間的區(qū)別就是非阻塞IO的應用,需要不斷的去訪問內(nèi)核獲取數(shù)據(jù)(當然了,每一次訪問都是有求必應,能取到數(shù)據(jù)),但不一定能取完; 而異步IO的特點就是,你告訴內(nèi)核取數(shù)據(jù),取完整了,我再一起發(fā)給應用程序。這就是Linux對異步IO的定義。

NIO、BIO、AIO與PHP實現(xiàn)的示例分析

那么再看到我們的Demo,這是一個簡單TCP server,一個TCP請求系統(tǒng)是能知道一個數(shù)據(jù)的包大小的,是否接收完畢,這是傳輸層要做的。而我們的應用層面,是接收到數(shù)據(jù)還要做合并,分包,以及數(shù)據(jù)轉(zhuǎn)碼。 這就和 AIO 數(shù)據(jù)結(jié)果必須是完整的,概率有些出入,(在系統(tǒng)層面顯然是完整的) . 在應用層面呢,一次性收到的不一定是完整的數(shù)據(jù),那么就還需要做額外代碼來解決合包,分包,沾包。這就是AIO實現(xiàn)Tcp Server的需要問題。

為了解決以上問題,就需要自定義TCP通訊協(xié)議。相當于自己開發(fā)RPC框架了。

那我們來看看Http呢,在應用層面有明確公開的協(xié)議(協(xié)議有頭無尾,標明了每次請求具體長度),并有豐富的實現(xiàn)。這就是一個非常適合采用AIO編程協(xié)議。而PHP的Event拓展,恰好有EventHttp實現(xiàn)。

話不多說,先上 Demo。

<?php  ...  /**   * event http 請求回調(diào)函數(shù)   *    * @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";  }

這里是一個回調(diào)函數(shù),入?yún)?shù)就是一個由 EventHttp 封裝的http請求對象。這就滿足了以上 調(diào)用時非阻塞,數(shù)據(jù)完全準備好后,再通知回調(diào)異步I/O。好,借助Event,PHP就實現(xiàn)了AIO.

以上是“NIO、BIO、AIO與PHP實現(xiàn)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI