Workerman是純PHP開發的開源高效能的非同步PHP socket框架。支援TCP長連接,支援websocket、MQTT等諸多協定。今天我們來介紹Workerman中的reusePort屬性,有需要的可以參考參考。
Workerman是高效能的PHP Socket伺服器框架。可以用Workerman 直接在TCP 層編程,基本的編程套路是:
$w = new Workerman\Worker('tcp://0.0.0.0:80'); $w->count = 4; $w->onMessage = function(Workerman\COnnection\TcpConnection $connection, array $data) { $connection->send('Hello World'); }; Worker::runAll();
在使用的過程中,不知道你是否留意過reusePort 這個參數,他默認被設置為false。這個參數有什麼用?什麼情況下我們需要把他設定為 true,從而提高效能呢?
關於reusePort 參數,Workerman官方的文檔是這麼解釋的:
開啟監聽端口復用後允許多個無親緣關係的進程監聽相同的端口,並且由系統核心做負載平衡,決定將socket連接交給哪個進程處理,避免了驚群效應,可以提升多進程短連接應用的性能。
如果沒有深入研究 Linux 網路編程,很難理解這句話。在此簡單解釋一下:
服務端程式通常會透過監聽伺服器上的某個連接埠號,來接收客戶端的請求。在Linux中,伺服器網路卡 連接埠號碼被抽象化了一個 Socket 。
為了提升效能,一般的服務端程式在執行時都有多個行程(俗稱Worker)監聽同一個Socket,在沒有客戶端連線到來的時候,這些Worker是處於掛起狀態的,不消耗CPU資源。
如果某一刻有一個客戶端連接到來,Linux 核心就會同時喚醒這些Worker,讓他們競爭去處理這個連接,
結果只有一個Worker 可以獲得處理這個連接的機會,其他Worker在競爭失敗後繼續回到掛起狀態。喚醒 Worker 的過程是消耗CPU資源的,Worker 數量越多,消耗的 CPU 資源就越多,造成了資源的浪費。這就是常說的 驚群效應。
你也許會問:為什麼不每次只喚醒一個Worker呢?很遺憾,Linux核心並沒有這樣的功能。
幸好,在 Linux 3.9 及以後的版本,加入 reuseport 特性。這個特性有什麼用呢?
在有 reuseport 之前,一個連接埠號碼只能被一個 Socket 監聽,有了 reuseport 之後,這個限制就被打破了:一個連接埠號碼可以被多個 Socket 同時監聽。
前面說到,Linux 核心無法做到一次只喚醒一個 Worker,但是,核心可以做到將客戶端連線均勻地傳送到監聽統一連接埠的一群 Socket 上。
如圖所示,每個 Worker 都有自己的 Socket,都監聽同一個連接埠。當有客戶端連線到來時,核心轉送連線到一個 Socket 上,而這個 Socket 只會喚醒自己隸屬的那個 Worker。這樣就很巧妙地解決了 驚群效應,提高了整體的性能。
由此,我們可以得出結論:如果你的 Linux 核心版本是 3.9 及以上的話,那麼在使用 Workerman 時,可以將 reusePort 設定為 true 提升程式運作效率。
雖然你只要在 Workerman 中把 reusePort 設定為 true,就能享受到 Linux 的這個進階特性。但 Workerman 的原始碼中,並不是開啟一個核心參數那麼簡單。 Workerman 為你隱藏了許多的設計細節,我們來研究下。
Worker
類別是Workerman 裡最主要的類,其中有一個listen()
函數:
protected function listen() { ... if (!$this->_mainSocket) { ... $this->_mainSocket = stream_socket_server(...); ... } ... }
#listen()
函數的作用就是在目前處理程序建立一個Socket 並開始監聽請求。
當reusePort 為false 時,主程序在創建Worker 之前就呼叫了listen()
函數:
protected function initWorkers() { .... if (!$worker->reusePort) { $worker->listen(); } .... }
隨後主程序通過pcntl_fork () 創建Worker。 pcntl_fork() 有個特性:建立出來的子行程(Worker)中的變數都是父行程複製而來的,包括父行程所建立的 mainSocket。所以,當reusePort為∗∗false∗∗時,所有的Worker都會複製父行程的mainSocket。所以,當reusePort為∗∗false∗∗時,所有的Worker都會複製父行程的_mainSocket,也也就是共用一個 Socket。
而當 reusePort 為 true 時,情況就不同了。主程序在建立Worker 前不會呼叫listen()
,而是在建立完Worker 後由每個Worker 自行發起listen()
呼叫:
protected static function forkOneWorkerForLinux($worker) { ... $pid = pcntl_fork(); if ($pid === 0) { if ($worker->reusePort) { $worker->listen(); } ... } ... }
這樣的結果就是,每個子進程(Worker)都創建了自己的Socket。
最後還有一點,如果想要核心開啟 reuseport 功能,需要手動設定 Socket 的 context:
if ($this->reusePort) { $context = stream_context_create(); stream_context_set_option($context, 'socket', 'so_reuseport', 1); }
推荐学习:php视频教程
以上是Workerman中你不得不知道的屬性reusePort的詳細內容。更多資訊請關注PHP中文網其他相關文章!