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

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

創(chuàng)新互聯(lián)公司10多年成都定制網(wǎng)站服務(wù);為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及高端網(wǎng)站定制服務(wù),成都定制網(wǎng)站及推廣,對(duì)護(hù)欄打樁機(jī)等多個(gè)行業(yè)擁有豐富建站經(jīng)驗(yàn)的網(wǎng)站建設(shè)公司。

什么BIO、NIO、AIO

BIO 同步阻塞I/O。

有小伙伴又要問了啥叫 同步,啥叫阻塞啊?

同步/異步 阻塞/非阻塞

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

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

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

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

以上就是這四個(gè)詞匯的解釋,那么放到計(jì)算機(jī)IO上,比較接地氣的解釋

BIO (Blocking I/O)

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

NIO (No-Blocking I/O)

同步非阻塞的I/O

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

AIO (Asynchronous I/O)

異步非阻塞IO

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

異步 阻塞 IO

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

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

偽異步 IO

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

最后總結(jié)一下:

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

區(qū)別

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

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

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

而客戶的實(shí)際體驗(yàn)式是"異步"的,那是因?yàn)楹髞韮?yōu)化了,CGI 程序能夠自我fork進(jìn)程的達(dá)到同時(shí)響應(yīng)多個(gè)http請(qǐng)求的效果。

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

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

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

  •  商品可售否

  •  庫(kù)存數(shù)量

  •  用戶余額

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

  •  獎(jiǎng)券有效性

  •  ...

按照一般做法就是一步步驗(yàn)證,上一個(gè)檢查完了,再進(jìn)行下一個(gè)檢查,這就是 阻塞 的方式。

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

PHP 能做什么

PHP 與 BIO 實(shí)現(xiàn)

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

  •  調(diào)整Nginx配置

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

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

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

NIO、BIO、AIO與PHP實(shí)現(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

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

最后的結(jié)果如下

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

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

<?php  sleep(5);

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

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

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

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

好的,我們?cè)僮鲆粋€(gè)實(shí)驗(yàn)。把以上nginx,php-fpm配置中1改成2.然后我們打開三個(gè)網(wǎng)頁(yè),同時(shí)訪問試試看。

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

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

PHP 與 NIO 實(shí)現(xiàn)

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

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

I/O 多路復(fù)用

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

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

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

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

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

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

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

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

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

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

NIO 客戶端

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

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

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

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

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

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

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

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

執(zhí)行效果如下:

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

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

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

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

PHP 與 AIO

PHP 異步&非阻塞 編碼。

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

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

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

<?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() , 這是在一個(gè)循環(huán)里讀,這是一個(gè)阻塞讀取。這的系統(tǒng)函數(shù)的響應(yīng)速度是受系統(tǒng)IO影響的。

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

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

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

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

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

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

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

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

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

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

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

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

話不多說,先上 Demo。

<?php  ...  /**   * event http 請(qǐng)求回調(diào)函數(shù)   *    * @param   \EventHttpRequest   $req    Http請(qǐng)求對(duì)象   */  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";  }

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

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

分享文章:NIO、BIO、AIO與PHP實(shí)現(xiàn)的示例分析
URL分享:http://muchs.cn/article32/ijdisc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、建站公司、手機(jī)網(wǎng)站建設(shè)、服務(wù)器托管、用戶體驗(yàn)網(wǎng)站營(yíng)銷

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運(yùn)營(yíng)