内容简介:前篇:上面一篇文章中,我們有提到兩種 php 的 web 運行模式使用 moduel 與 fast_cgi 模式 的 web server 模式基本上會有兩個問題存在。
前篇: PHP 的 Web 運行原理 ( 1 )
上面一篇文章中,我們有提到兩種 php 的 web 運行模式 moduel
與 fast_cgi
模式,它們在某種情況下,都會有些問題,而我們這篇文章就是要來理解是碰到什麼問題,然後又是如何解決呢 ?
- Reactor 模式想解決的問題
- Reactor 模式原理
- Reactor 的使用注意事項
Reactor 模式想解決的問題
使用 moduel 與 fast_cgi 模式 的 web server 模式基本上會有兩個問題存在。
1. 高併發請求,會爆 !
如下面這張圖一樣,它每一個 http 請求都需要使用一個 process 或是 thread 來進行處理,而每一台機器的 process 與 thread 的數量都有限制,且操作系統進行 process 或 thread 上文文切換時非常耗的資源。
2. 服務如果是大量 I/O 操作會很浪費資源 !
ex. 讀 db 或 redis 啥的
主要耗資源的地方在於,每個 process 開啟後,大部份的時間者是在等待 I/O 的處理,而 CPU 都是閒在那。
上面兩個是看到的現象,而真正的問題點在於 :
為什麼每個請求都需要開啟一個 process 或 thread 來處理呢 ?
主要的原因在於,在 linux 底層中,我們使用了阻塞 I/O 方法,來讀取 http 傳送過來的資料。如下範例程式碼。 socket网络编程中read与recv
data = read(sockfd); // 這裡會阻塞整個 process handler(data);
它就會一直停在那一行 read 等待資料進來,也就是說整個 process 為了監聽 socket 有沒有資料,就卡在那了。
這也是為什麼每一條 http(I/O) 請求,都需要啟一個 process 或 thread 來處理某條 socket 的原因。
備註:
每一個 http 請求基本上就是會建立一條 socket,而 sockfd 又代表此 socket 的 File descriptor,不熟悉網路的友人可以用上面這幾個關鍵字來查詢。
Reactor 模式原理
基本上高併發 I/O 這個問題,目前比較常用的解法為 :
Reactor 模式
reactor 模式可以幫助我們建立非阻塞 I/O 模式,也就是不需要開多個 thread 或 process 來處理 I/O 操作。
它的概念如下圖 :
上圖架構中,最重要的就是 I/O 多路復用 (Multiplexing)這部份,基本上它是系統底層所提供的功能,它可以幫助我們監控所有有註冊的 socket,當它有事件進來以後,就會由指定的 handler 來進行處理。
下面為 reactor 模式的概念碼。而事實上這就是 nodejs 中我們常聽到的 event loop 的機制。
但這裡要注意,epoll_wait 本身是一個阻塞方法,也就是說執行它,整個 process 會被卡住。有寫過 nodejs 的人在這裡應該會有疑問,等等會解答。
while(true){ events = epoll_wait(); // 這裡會取得到 I/O 讀取資料的事件資訊 ex. read for (int i=0; i < events.size(); i++){ handler(events[i]); } }
備註 :
I/O 多路復用 (Multiplexing) 不同的平台有不同的實作,epoll(linux)、kqueue(Mac)、IOCP(Window)。
Multiplexing 與 Handler 是不同的 process 嗎 ? 還是相同的 ?
答案是都可以。
像 nodejs 就是屬於 multiplexing 與 handler 都是在同一個 process 運行,而 php swoole 則是屬於 multiplexing 與 handler 在不同 process 運行。
Multiplexing 不是需要一個 process 一直監聽所有 socket 嗎 ? 那為什麼 nodejs 可以單個 process 同時做到監聽與處理呢 ?
嗯之前我也有這個疑問。
當初我的想法是如下概念碼一樣,它會在 epoll_wait 阻塞住整個 process 來監聽,然後在有事件時,丟給某個 thread 或其它 process 的 handler 來處理。
while(true){ events = epoll_wait(); // 它會一直停在這裡,等某個 socket 有事件進來。 for (int i=0; i < socket.size(); i++){ handler(events[i]) } }
epoll_wait 的確是阻塞 I/O 操作沒錯,但是它事實上有提供一個參數 timeout,也就是如果這段時間沒有資料,它就會回傳個 null 或啥 -1 的,反正就是和你說沒資料進來,而這時你就可以繼續往下處理。
備註: 可以拉到最下看看 libvu 所提供的 timeout 計算方式。
while(true){ events = epoll_wait(timeout); // 注意 timeout 。 for (int i=0; i < sockets.size(); i++){ handler(sockets[i]) } // 取出接下來 event queue 中要處理的工作。 worker = queue.poll(); handler(worker); }
Reactor 模式的使用注意
下面為 reactor 模式的概念碼。
while(true){ events = epoll_wait(); // 這裡會取得到 I/O 讀取資料的事件資訊 ex. read for (int i=0; i < events.size(); i++){ handler(events[i]); } }
假設我們在 handler 執行一段 cpu 密集工作的話會如何呢 ?
嗯就是會整個 process 卡住,也就是說除非計算完成,不然什麼事情都不能做。
所以別忘了 reactor 的使用重點 :
reactor 是用來解決 I/O 密集的模式,但無法處理 CPU 密集的工作
所以在基本上要先理解清除你的系統是大部份的工作才能決定要什麼模式來處理。
- CPU 密集處理 => multi process 或 multi thread 模式。
- I/O 密集處理 => reactor 模式
結論
本篇文章中我們說到以下幾個重點:
Reactor 模式想解決的問題
大量 I/O 操作的情況,會導致傳統的 multi process web 模式倒站問題。
Reactor 架構
重點就是 I/O 多路復用
有了它我們才能一個 process 監控多個 socket。
Reactor 的使用注意
在 reactor 模式的系統下,不要執行大量 cpu 運算的工作,會導致整個 process 卡住,大量 cpu 運算請另開 process 或 thread 來處理。
參考資料
- Linux 的 IO 通信 以及 Reactor 线程模型浅析
- Node.js design pattern : Reactor (Event Loop)
- Reactor模式
- 通俗地讲,Netty 能做什么?
- nodejs 异步I/O和事件驱动
- 高性能IO之Reactor模式
- Java NIO 系列文章之 浅析Reactor模式
- 非阻塞IO与reactor模式
- 多路复用、非阻塞、线程与协程
libuv 的 timeout 計算參考
下面為 libuv 的 timeout 計算的方法,
int uv_backend_timeout(const uv_loop_t* loop) { if (loop->stop_flag != 0) return 0; if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop)) return 0; if (!QUEUE_EMPTY(&loop->idle_handles)) return 0; if (!QUEUE_EMPTY(&loop->pending_queue)) return 0; if (loop->closing_handles) return 0; return uv__next_timeout(loop); }
而下面為 uv__next_timeout 的源始碼。
int uv__next_timeout(const uv_loop_t* loop) { const struct heap_node* heap_node; const uv_timer_t* handle; uint64_t diff; heap_node = heap_min(timer_heap(loop)); if (heap_node == NULL) return -1; /* block indefinitely */ handle = container_of(heap_node, uv_timer_t, heap_node); if (handle->timeout <= loop->time) return 0; diff = handle->timeout - loop->time; if (diff > INT_MAX) diff = INT_MAX; return (int) diff; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- java中的阻塞原理及实现
- Node.js 指南(阻塞与非阻塞概述)
- Node.js 回调函数 阻塞与非阻塞
- 明明白白学 同步、异步、阻塞与非阻塞
- 从 Linux 源码看 socket 的阻塞和非阻塞
- 分布式系统关注点——阻塞与非阻塞有什么区别?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。