Swoole+Lumen:同步編程風(fēng)格調(diào)用MySQL異步查詢

網(wǎng)絡(luò)編程一直是PHP的短板校焦,盡管Swoole擴(kuò)展彌補(bǔ)了這個(gè)缺陷运吓,但是其編程風(fēng)格偏向了NodeJS或GoLang跌穗,與原本的同步編程風(fēng)格迥然相異佃声。目前PHP的大部分主流應(yīng)用框架依然是同步編程風(fēng)格,所以一直在探索Swoole與同步編程結(jié)合的途徑迄委。
lumen-swoole-http正是連接同步編程Lumen和異步編程Swoole的一座橋梁褐筛,有興趣可以關(guān)注一下。

LNMP的不足

LNMP是經(jīng)典的Web應(yīng)用架構(gòu)組合叙身,雖然(Linux渔扎、NginX、MySQL和PHP-FPM)四者各種是優(yōu)秀的系統(tǒng)或軟件信轿,但是組合到一起的總體性能并不盡人意晃痴,明顯的不是1+1+1+1>4,而是4+3+2+1<1虏两。Linux系統(tǒng)無(wú)可厚非愧旦,主要問(wèn)題出現(xiàn)在:

從NginX到PHP-FPM

NginX利用IO多路復(fù)用機(jī)制epoll,極大地減少了IO阻塞等待定罢,可以輕松應(yīng)對(duì)C10K∨蕴保可是每次NginX將用戶請(qǐng)求傳遞給PHP-FPM時(shí)祖凫,PHP-FPM總是需要從新加載PHP項(xiàng)目代碼:創(chuàng)建執(zhí)行環(huán)境,讀取PHP文件和代碼解析酬凳、編譯等操作一次又一次的重復(fù)執(zhí)行惠况,造成不小的消耗。

從PHP-FPM到MySQL

由于PHP代碼本身是同步執(zhí)行宁仔,PHP-FPM連接MySQL查詢數(shù)據(jù)時(shí)稠屠,只能空閑等待MySQL返回查詢結(jié)果。一個(gè)查詢語(yǔ)句執(zhí)行時(shí)間可能會(huì)需要幾秒鐘翎苫,期間PHP-FPM若是能暫時(shí)放下當(dāng)前用戶慢查詢請(qǐng)求权埠,而去處理其他用戶請(qǐng)求,效率必然有所提高煎谍。

Swoole HTTP服務(wù)器

Swoole HTTP服務(wù)器也采用了epoll機(jī)制攘蔽,運(yùn)行性能與NginX相比,雖不及呐粘,猶未遠(yuǎn)满俗。不過(guò)Swoole HTTP服務(wù)器嵌入PHP中作為其一部分转捕,可以直接運(yùn)行PHP,完全可以取代NginX + PHP-FPM組合唆垃。

以目前流行的為框架Lumen(Laravel的子框架)為例五芝,用Swoole HTTP服務(wù)器運(yùn)行Lumen項(xiàng)目十分簡(jiǎn)單,只需要在$worker->onRequest($request, $response)(收到用戶請(qǐng)求)時(shí)將$request傳給Lumen處理辕万,$response再將Lumen的處理結(jié)果返回給用戶枢步,而且$worker的整個(gè)生命周期里只會(huì)加載一次Lumen項(xiàng)目代碼,沒(méi)有多余的磁盤(pán)IO和PHP代碼編譯的開(kāi)銷蓄坏。

壓力測(cè)試

在4GB+4Core的虛擬機(jī)下价捧,測(cè)試HTTP服務(wù)器的靜態(tài)輸出:

  • 2000客戶端并發(fā)500000請(qǐng)求,不開(kāi)啟HTTP Keepalive涡戳,平均QPS:
NginX + HTML               QPS:25883.44
NginX + PHP-FPM + Lumen    QPS:828.36
Swoole + Lumen             QPS:13647.75
  • 2000客戶端并發(fā)500000請(qǐng)求结蟋,開(kāi)啟HTTP Keepalive,平均QPS:
NginX + HTML               QPS:86843.11
NginX + PHP-FPM + Lumen    QPS:894.06
Swoole + Lumen             QPS:18183.43

可以看出渔彰,Swoole + Lumen組合的執(zhí)行效率遠(yuǎn)高于NginX + PHP-FPM + Lumen組合嵌屎。

異步MySQL客戶端

以上都是鋪墊,以下才是整篇文章的重點(diǎn)??????

一個(gè)PHP應(yīng)用要做的事不會(huì)是單純的數(shù)據(jù)計(jì)算和數(shù)據(jù)輸出恍涂,更多的是與數(shù)據(jù)庫(kù)數(shù)據(jù)交互宝惰。以MySQL數(shù)據(jù)庫(kù)為例,在只有一個(gè)PHP進(jìn)程的情況再沧,有10個(gè)用戶同時(shí)請(qǐng)求執(zhí)行select sleep(1);(耗時(shí)1秒)查詢語(yǔ)句尼夺,若是使用MySQL同步查詢,那么總耗時(shí)至少是10秒炒瘸;若是使用MySQL異步查詢淤堵,那么總耗時(shí)可能壓縮到1到2秒內(nèi)。

在PHP應(yīng)用中能夠?qū)崿F(xiàn)數(shù)據(jù)庫(kù)異步查詢顷扩,才能更大的突破性能瓶頸拐邪。

雖然Swoole提供了異步MySQL客戶端,但是其異步編程風(fēng)格與Lumen這種同步編程風(fēng)格的項(xiàng)目框架沖突隘截,那么有沒(méi)有可能在同步編程風(fēng)格代碼中調(diào)用異步MySQL客戶端呢扎阶?

一開(kāi)始我覺(jué)得這是不可能的,直到我看到了這片文章:Cooperative multitasking using coroutines (in PHP!)婶芭。當(dāng)然东臀,我看的是中文版: 在PHP中使用協(xié)程實(shí)現(xiàn)多任務(wù)調(diào)度,文中提到了PHP5.5加入的一個(gè)新功能:yield雕擂。

Yield

yield是個(gè)動(dòng)詞啡邑,意思是“生成”,PHP中yield生出的東西叫Generator井赌,意思是“生成器”??????谤逼。

個(gè)人理解是:yield將當(dāng)前執(zhí)行的上下文作為當(dāng)前函數(shù)的結(jié)果返回(yield必須在函數(shù)中使用)贵扰。

在系統(tǒng)層面,各個(gè)進(jìn)程的運(yùn)行秩序由CPU調(diào)度流部;而有了yield戚绕,在PHP進(jìn)程內(nèi),程序員可以自由調(diào)度各個(gè)代碼塊的執(zhí)行順序枝冀。比如舞丛,當(dāng)“發(fā)現(xiàn)”當(dāng)前用戶請(qǐng)求的MySQL查詢將會(huì)花費(fèi)較多的時(shí)間,那么可以將當(dāng)前執(zhí)行上下文記錄起來(lái)果漾,交給異步MySQL客戶端處理(與用戶請(qǐng)求相關(guān)的$request$response也傳遞過(guò)去)球切,而主進(jìn)程繼續(xù)處理下一個(gè)用戶請(qǐng)求。

約定聲明

前面用了“發(fā)現(xiàn)”這個(gè)詞绒障,當(dāng)然程序不可能智能地發(fā)現(xiàn)還沒(méi)執(zhí)行的查詢語(yǔ)句將會(huì)是個(gè)慢查詢吨凑,我們需要一些約定和聲明。
Lumen框架是經(jīng)典的MVC模式户辱,我們約定C即Controller是處理用戶請(qǐng)求的最后一步——Controller接受用戶請(qǐng)求$request并返回響應(yīng)$response鸵钝。同時(shí)我們聲明一個(gè)類,叫SlowQuery庐镐,這個(gè)類十分簡(jiǎn)單(具體請(qǐng)參見(jiàn)SlowQuery.php):

<?php
namespace BL\SwooleHttp\Database;

class SlowQuery
{
    public $sql = '';

    public function __construct($sql)
    {
        $this->sql    = $sql;
    }
}

比如恩商,Lumen項(xiàng)目中有這么一個(gè)Controller:

<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use DB;

class TestController extends Controller
{
    public function test()
    {
        $a = DB::select('select sleep(1);');
        response()->json($a);
    }
}

上面的DB::select使用的同步MySQL客戶端查詢,我們用SlowQuery對(duì)象替換它:

<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use BL\SwooleHttp\Database\SlowQuery;

class TestController extends Controller
{
    public function test()
    {
        $a = yield new SlowQuery('select sleep(1);');
        response()->json($a);
    }
}

以Swoole HTTP服務(wù)器運(yùn)行Lumen項(xiàng)目時(shí)必逆,我們一定會(huì)獲取Controller的返回結(jié)果怠堪。Controller的返回結(jié)果一般可以直接包裝成Lumen響應(yīng)返回給用戶的,但返回結(jié)果若是一個(gè)生成器Generator對(duì)象名眉,而且其當(dāng)前值是一個(gè)慢查詢SlowQuery對(duì)象的話研叫,那么我們可以取出SlowQuery對(duì)象的sql屬性,交由異步MySQL客戶端執(zhí)行璧针;在異步查詢的回調(diào)函數(shù)中將查詢結(jié)果放回Generator對(duì)象存儲(chǔ)的上下文中運(yùn)行,得到最后結(jié)果才返回給用戶渊啰;而主進(jìn)程沒(méi)有阻塞探橱,可以繼續(xù)處理其他用戶請(qǐng)求。

當(dāng)然绘证,如果想用Eloquent ORM隧膏,那也很簡(jiǎn)單:我們先繼承Lumen的Model,封裝成一個(gè)新的Model類(具體參見(jiàn)Model.php)嚷那,應(yīng)用中的數(shù)據(jù)模型都繼承于新的Model胞枕,Controller就可以這樣寫(xiě):

<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use DB;

class TestController extends Controller
{
    public function test()
    {
        $a = yield User::select(DB::raw('sleep(1)'))->yieldGet(); // 注意User須繼承自\BL\SwooleHttp\Database\Model
        response()->json($a);
    }
}

以上三個(gè)Controller最終產(chǎn)出的用戶響應(yīng)都是一樣的,不過(guò)后兩者使用的是異步MySQL客戶端魏宽,效率更高腐泻。

任務(wù)調(diào)度器

當(dāng)然决乎,我們還需要一個(gè)任務(wù)調(diào)度器來(lái)執(zhí)行這些生成器,任務(wù)調(diào)度器的實(shí)現(xiàn)方法 在PHP中使用協(xié)程實(shí)現(xiàn)多任務(wù)調(diào)度文中“多任務(wù)協(xié)作”章節(jié)里有介紹派桩,這里不展開(kāi)构诚。
Lumen框架中的代碼保持了同步編程風(fēng)格,而任務(wù)調(diào)度器中使用了異步編程風(fēng)格來(lái)調(diào)用異步MySQL客戶端铆惑。任務(wù)調(diào)度器是在Swoole HTTP服務(wù)器層面使用的范嘱,具體參見(jiàn)Service.php

連接限制

其實(shí)员魏,每開(kāi)啟一個(gè)Swoole異步MySQL客戶端丑蛤,主進(jìn)程就會(huì)新建一個(gè)線程連接MySQL,若是建立太多連接(線程)撕阎,會(huì)增加自身服務(wù)器的壓力受裹,也會(huì)增加MySQL數(shù)據(jù)庫(kù)服務(wù)器的壓力。
這種利用yield來(lái)調(diào)用異步MySQL客戶端處理慢查詢而產(chǎn)生的線程闻书,暫且稱它為“慢查詢協(xié)程”名斟。
為了限制數(shù)據(jù)庫(kù)連接數(shù)量,我們可以設(shè)置一個(gè)全局變量記錄可新建慢查詢協(xié)程的數(shù)量MAX_COROUTINE魄眉,開(kāi)啟一個(gè)異步MySQL客戶端時(shí)讓其減一砰盐,關(guān)閉一個(gè)異步MySQL客戶端時(shí)讓其加一;當(dāng)用戶請(qǐng)求慢查詢時(shí)坑律,MAX_COROUTINE大于0則由異步MySQL客戶端處理岩梳,MAX_COROUTINE等于0時(shí)則由主進(jìn)程“硬著頭皮”自己處理。

壓力測(cè)試

在4GB+4Core的虛擬機(jī)下晃择,測(cè)試HTTP服務(wù)器與數(shù)據(jù)庫(kù)讀寫(xiě):

一般的快速查詢和快速寫(xiě)入測(cè)試:

  • 200并發(fā)50000請(qǐng)求讀冀值,利用HTTP Keepalive,平均QPS:
NginX + PHP-FPM + Lumen + MySQL    QPS:521.56
Swoole + Lumen + MySQL             QPS:7509.99
  • 200并發(fā)50000請(qǐng)求寫(xiě)宫屠,利用HTTP Keepalive列疗,平均QPS:
NginX + PHP-FPM + Lumen + MySQL    QPS:449.44
Swoole + Lumen + MySQL             QPS:1253.93

慢查詢協(xié)程測(cè)試:

  • 16worker的Swoole HTTP服務(wù)器,并發(fā)執(zhí)行select sleep(1);請(qǐng)求的最大效率是15.72rps浪蹂;
  • 16worker x 10coroutine的Swoole HTTP服務(wù)器抵栈,并發(fā)執(zhí)行select sleep(1);請(qǐng)求的最大效率是151.93rps。

這里為什么說(shuō)最大效率呢坤次?因?yàn)楫?dāng)并發(fā)量遠(yuǎn)大于worker數(shù)目 x coroutine數(shù)目時(shí)古劲,可開(kāi)啟慢查詢協(xié)程的Swoole HTTP服務(wù)器的效率會(huì)逐漸跌向普通Swoole HTTP服務(wù)器。

select sleep(1);查詢語(yǔ)句耗時(shí)1秒缰猴,每個(gè)用戶請(qǐng)求都需要1秒時(shí)間來(lái)處理产艾;不過(guò),16進(jìn)程的、每個(gè)進(jìn)程可開(kāi)啟10個(gè)慢查詢協(xié)程的Swoole HTTP服務(wù)器的每秒最多可以處理160個(gè)用戶請(qǐng)求闷堡,而16進(jìn)程的普通Swoole HTTP服務(wù)器每秒最多只能處理16個(gè)用戶請(qǐng)求隘膘。

延伸

其實(shí)利用yield,我們還可以實(shí)現(xiàn)各種各樣的“協(xié)程”缚窿。比如棘幸,Swoole2.1版本已經(jīng)開(kāi)始支持go函數(shù)與通道,后續(xù)我們可能還可以將Lumen Controller中一些IO阻塞的操作的上下文移至go函數(shù)里執(zhí)行倦零,這樣既保留了同步編程的風(fēng)格误续,由達(dá)到異步執(zhí)行的性能。

最后

以上理論扫茅,已經(jīng)在lumen-swoole-http項(xiàng)目中實(shí)現(xiàn)蹋嵌。
lumen-swoole-http是連接同步編程Lumen和異步編程Swoole的一座橋梁,可以幫助原生PHP的Lumen應(yīng)用項(xiàng)目快速遷移到Swoole HTTP服務(wù)器上葫隙;當(dāng)然也可以快速遷移回去??栽烂。
有興趣的同學(xué)可以嘗試使用:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恋脚,隨后出現(xiàn)的幾起案子腺办,更是在濱河造成了極大的恐慌,老刑警劉巖糟描,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怀喉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡船响,警方通過(guò)查閱死者的電腦和手機(jī)躬拢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)见间,“玉大人聊闯,你說(shuō)我怎么就攤上這事∶姿撸” “怎么了菱蔬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)史侣。 經(jīng)常有香客問(wèn)我汗销,道長(zhǎng),這世上最難降的妖魔是什么抵窒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮叠骑,結(jié)果婚禮上李皇,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好掉房,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布茧跋。 她就那樣靜靜地躺著,像睡著了一般卓囚。 火紅的嫁衣襯著肌膚如雪瘾杭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天哪亿,我揣著相機(jī)與錄音粥烁,去河邊找鬼。 笑死蝇棉,一個(gè)胖子當(dāng)著我的面吹牛讨阻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播篡殷,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钝吮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了板辽?” 一聲冷哼從身側(cè)響起奇瘦,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎劲弦,沒(méi)想到半個(gè)月后耳标,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓶您,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年麻捻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呀袱。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贸毕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夜赵,到底是詐尸還是另有隱情明棍,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布寇僧,位于F島的核電站摊腋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嘁傀。R本人自食惡果不足惜兴蒸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望细办。 院中可真熱鬧橙凳,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至坚踩,卻和暖如春荡灾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞬铸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工批幌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赴捞。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓逼裆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赦政。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胜宇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 并發(fā)IO問(wèn)題一直是服務(wù)器端編程中的技術(shù)難題,從最早的同步阻塞直接Fork進(jìn)程恢着,到Worker進(jìn)程池/線程池桐愉,到現(xiàn)在...
    零一間閱讀 1,714評(píng)論 1 34
  • 出處:韓天峰 網(wǎng)址:rango.swoole.com/archives/508 并發(fā)IO問(wèn)題一直是后端編程中的技術(shù)...
    meng_philip123閱讀 2,402評(píng)論 1 38
  • 更改ip和dnsVi /etc/sysconfig/network-scripts/ifcfg-eth0vi /...
    Xwei_閱讀 1,816評(píng)論 0 3
  • 大家好!我是一名高校老師,也是一位媽媽掰派,今日分享一下學(xué)習(xí)Tyger課程體會(huì)从诲。我和女兒一起學(xué)習(xí)了tyger老師課程5...
    wangIiIy閱讀 666評(píng)論 2 2
  • 這世界總是無(wú)法預(yù)料地前行。很多事情的發(fā)生靡羡,并不遵循因果系洛。比如突然的愛(ài)和莫名其妙的厭倦。人們終其一生都在試圖掌控或者...
    南煙客丶江郎閱讀 244評(píng)論 0 0