workerman是一款純php開(kāi)發(fā)的開(kāi)源高性能的異步php socket框架。支持tcp長(zhǎng)連接,支持websocket、mqtt等諸多協(xié)議。今天我們來(lái)介紹一下workerman中的reuseport屬性,有需要的可以參考參考。
Workerman是一個(gè)高性能的PHP Socket服務(wù)器框架。可以用 Workerman 直接在 TCP 層編程,基本的編程套路是:
$w?=?new?WorkermanWorker('tcp://0.0.0.0:80'); $w->count?=?4; $w->onMessage?=?function(WorkermanCOnnectionTcpConnection?$connection,?array?$data)?{ ????$connection->send('Hello?World'); }; Worker::runAll();
在使用的過(guò)程中,不知道你是否留意過(guò) reusePort 這個(gè)參數(shù),他默認(rèn)被設(shè)置為 false。這個(gè)參數(shù)有什么用?什么情況下我們需要把他設(shè)置為 true,從而提高性能呢?
1. reuseport 的作用
關(guān)于 reusePort 參數(shù),Workerman官方的文檔是這么解釋的:
開(kāi)啟監(jiān)聽(tīng)端口復(fù)用后允許多個(gè)無(wú)親緣關(guān)系的進(jìn)程監(jiān)聽(tīng)相同的端口,并且由系統(tǒng)內(nèi)核做負(fù)載均衡,決定將socket連接交給哪個(gè)進(jìn)程處理,避免了驚群效應(yīng),可以提升多進(jìn)程短連接應(yīng)用的性能。
如果沒(méi)有深入研究過(guò) Linux 網(wǎng)絡(luò)編程,很難理解這句話。在此簡(jiǎn)單解釋一下:
服務(wù)端程序通常通過(guò)監(jiān)聽(tīng)服務(wù)器上的某個(gè)端口號(hào),來(lái)接收客戶(hù)端的請(qǐng)求。在Linux中,服務(wù)器網(wǎng)卡 + 端口號(hào)被抽象成了一個(gè) Socket 。
為了提升性能,一般的服務(wù)端程序在運(yùn)行時(shí)都有多個(gè)進(jìn)程(俗稱(chēng) Worker)監(jiān)聽(tīng)同一個(gè) Socket,在沒(méi)有客戶(hù)端連接到來(lái)的時(shí)候,這些Worker是處于掛起狀態(tài)的,不消耗CPU資源。
如果某一刻有一個(gè)客戶(hù)端連接到來(lái),Linux 內(nèi)核就會(huì)同時(shí)喚醒這些 Worker,讓他們競(jìng)爭(zhēng)去處理這個(gè)連接,
結(jié)果只有一個(gè) Worker 可以獲得處理這個(gè)連接的機(jī)會(huì),其他Worker在競(jìng)爭(zhēng)失敗后繼續(xù)回到掛起狀態(tài)。喚醒 Worker 的過(guò)程是要消耗CPU資源的,Worker 數(shù)量越多,消耗的 CPU 資源就越多,造成了資源的浪費(fèi)。這就是常說(shuō)的 驚群效應(yīng)。
你也許會(huì)問(wèn):為什么不每次只喚醒一個(gè)Worker呢?很遺憾,Linux內(nèi)核并沒(méi)有這樣的功能。
幸好,在 Linux 3.9 及以后的版本,加入 reuseport 特性。這個(gè)特性有什么用呢?
在有 reuseport 之前,一個(gè)端口號(hào)只能被一個(gè) Socket 監(jiān)聽(tīng),有了 reuseport 之后,這個(gè)限制就被打破了:一個(gè)端口號(hào)可以被多個(gè) Socket 同時(shí)監(jiān)聽(tīng)。
前面說(shuō)到,Linux 內(nèi)核沒(méi)法做到一次只喚醒一個(gè) Worker,但是,內(nèi)核可以做到將客戶(hù)端連接均勻地發(fā)送到監(jiān)聽(tīng)統(tǒng)一端口的一群 Socket 上。
如圖所示,每個(gè) Worker 都有自己的 Socket,都監(jiān)聽(tīng)同一個(gè)端口。當(dāng)有客戶(hù)端連接到來(lái)時(shí),內(nèi)核轉(zhuǎn)發(fā)連接到一個(gè) Socket 上,而這個(gè) Socket 只會(huì)喚醒自己隸屬的那個(gè) Worker。這樣就很巧妙地解決了 驚群效應(yīng),提高了整體的性能。
由此,我們可以得出結(jié)論:如果你的 Linux 內(nèi)核版本是 3.9 及以上的話,那么在使用 Workerman 時(shí),可以將 reusePort 設(shè)置為 true 提升程序運(yùn)行效率。
2. Workerman 如何利用 reuseport
雖然你只要在 Workerman 中把 reusePort 設(shè)置為 true,就能享受到 Linux 的這個(gè)高級(jí)特性。但 Workerman 的源碼中,并不只是開(kāi)啟一個(gè)內(nèi)核參數(shù)那么簡(jiǎn)單。Workerman 為你隱藏了許多的設(shè)計(jì)細(xì)節(jié),我們來(lái)研究下。
Worker 類(lèi)是 Workerman 里最主要的類(lèi),其中有個(gè) listen() 函數(shù):
protected?function?listen() { ????... ????if?(!$this->_mainSocket)?{ ????????... ????????$this->_mainSocket?=?stream_socket_server(...); ????????... ????} ????... }
listen() 函數(shù)的作用就是在當(dāng)前進(jìn)程創(chuàng)建一個(gè) Socket 并開(kāi)始監(jiān)聽(tīng)請(qǐng)求。
當(dāng) reusePort 為 false 時(shí),主進(jìn)程在創(chuàng)建 Worker 之前就調(diào)用了 listen() 函數(shù):
protected?function?initWorkers()?{ ????.... ????if?(!$worker->reusePort)?{ ????????$worker->listen(); ????} ????.... }
隨后主進(jìn)程通過(guò) pcntl_fork() 創(chuàng)建 Worker。pcntl_fork() 有個(gè)特性:創(chuàng)建出來(lái)的子進(jìn)程(Worker)中的變量都是父進(jìn)程復(fù)制而來(lái)的,包括父進(jìn)程創(chuàng)建的 mainSocket。所以,當(dāng)reusePort為??false??時(shí),所有的Worker都復(fù)制父進(jìn)程的mainSocket。所以,當(dāng)reusePort為??false??時(shí),所有的Worker都復(fù)制父進(jìn)程的_mainSocket,也即共用一個(gè) Socket。
而當(dāng) reusePort 為 true 時(shí),情況就不同了。主進(jìn)程在創(chuàng)建 Worker 前不會(huì)調(diào)用 listen(),而是在創(chuàng)建完 Worker 后由每個(gè) Worker 自行發(fā)起 listen() 調(diào)用:
protected?static?function?forkOneWorkerForLinux($worker)?{ ????... ????$pid?=?pcntl_fork(); ????if?($pid?===?0)?{ ????????if?($worker->reusePort)?{ ????????????$worker->listen(); ????????} ????????... ????} ????... }
這樣的結(jié)果就是,每個(gè)子進(jìn)程(Worker)都創(chuàng)建了自己的 Socket。
最后還有一點(diǎn),如果想要內(nèi)核開(kāi)啟 reuseport 功能,需要手動(dòng)設(shè)置 Socket 的 context:
if?($this->reusePort)?{ ????$context?=?stream_context_create(); ????stream_context_set_option($context,?'socket',?'so_reuseport',?1); }
推薦學(xué)習(xí):php視頻教程