php cli swoole mysql_[了解实践]Swoole、PHP与MySQL:连接池,swoole扩展实现真正的PHP数据库连接池。...
背景:swoole的出現(xiàn),包括PHP出現(xiàn)前,在新浪企業(yè)郵箱就有基于Sun Solaris 系統(tǒng)上面用c++寫Mysql的長(zhǎng)連接,那時(shí)候的長(zhǎng)連接是基于RPC實(shí)現(xiàn),對(duì)mysql那一端形成一個(gè)遠(yuǎn)程過(guò)程的調(diào)用,通過(guò)XDR數(shù)據(jù)結(jié)構(gòu)進(jìn)行解析mysql傳來(lái)的數(shù)據(jù)項(xiàng)(RPC也為sun最新提出并后來(lái)在linux上默認(rèn)支持),也就是說(shuō)像用戶登錄驗(yàn)證這一塊用Mysql的長(zhǎng)連接來(lái)實(shí)現(xiàn),提高其效率運(yùn)行相當(dāng)穩(wěn)定,后面這個(gè)系統(tǒng)遷移到了FreeBSD后,出現(xiàn)了mysql長(zhǎng)連接的服務(wù)經(jīng)常出現(xiàn)假死,也就是說(shuō)進(jìn)程還在,但是已經(jīng)連接不上mysql了,重新啟動(dòng)這個(gè)RPC服務(wù)又好了,原因未知,當(dāng)時(shí)我對(duì)c++不了解(現(xiàn)在也不太了解,只聽(tīng)說(shuō)要看是否形成coredump啥的),當(dāng)年我還寫過(guò)一個(gè)判斷死了就殺死,重啟動(dòng),判斷的程序也老半天回不來(lái),于是我又改成了一個(gè)多進(jìn)程,如果超時(shí)沒(méi)有回來(lái),就干掉那個(gè)進(jìn)程,重啟Rpc服務(wù),再后來(lái),這套C++的cgi被替換成了php,再后來(lái)基于FreeBSD的系統(tǒng)遷移到了Linux,也就是現(xiàn)在一直在linux上,linux也就強(qiáng)大了起來(lái),回想起來(lái),當(dāng)年一個(gè)登錄服務(wù)如此極致,現(xiàn)在都變成了直接查詢mysql了,這個(gè)長(zhǎng)連接技術(shù)有還有用嗎?我只能說(shuō)對(duì)有上千臺(tái)上萬(wàn)臺(tái)的服務(wù)器可能有用,能節(jié)省一定的機(jī)器成本罷。但是,追求技術(shù)永無(wú)止境,需要有這樣的一些東西來(lái)繁榮我們這個(gè)PHP的市場(chǎng),長(zhǎng)連接這個(gè)話題不再是Java做成了連接池,像c++也能做成連接池,像騰訊廣平就有c++團(tuán)隊(duì)還有寫cgi實(shí)現(xiàn)長(zhǎng)連接Mysql服務(wù),據(jù)說(shuō)前二年吧更多關(guān)注了H5,像實(shí)時(shí)技術(shù),比如Tail技術(shù)在web上的實(shí)現(xiàn),有轉(zhuǎn)向nodejs的趨勢(shì),而此時(shí)的PHP拿不出這樣的技術(shù),是很危險(xiǎn)的,有了swoole起到填補(bǔ)作用,我更多的是覺(jué)得官方應(yīng)該重視這個(gè)技術(shù),而不是形成一個(gè)擴(kuò)展,像H5的來(lái)到,像websocket的進(jìn)入,這些東西對(duì)于Node來(lái)講,從前端向后端的統(tǒng)一,而PHp呢?沒(méi)有誰(shuí)來(lái)解決,那么從用戶角度來(lái)講,開(kāi)發(fā)者用戶的流失或遷移,對(duì)PHP本身也是一個(gè)損失,但我還是說(shuō)PHP是最好的語(yǔ)言沒(méi)有之一,期望其能伴隨潮流,與時(shí)俱進(jìn)。
PHP的數(shù)據(jù)庫(kù)連接池一直以來(lái)都是一個(gè)難題,很多從PHP語(yǔ)言轉(zhuǎn)向Java的項(xiàng)目,大多數(shù)原因都是因?yàn)镴ava有更好的連接池實(shí)現(xiàn)。PHP的MySQL擴(kuò)展提供了長(zhǎng)連接的API,但在PHP機(jī)器數(shù)量較多,規(guī)模較大的情況下,mysql_pconnect非但不能節(jié)約MySQL資源,反而會(huì)加劇數(shù)據(jù)庫(kù)的負(fù)荷。
假設(shè)有100臺(tái)PHP的應(yīng)用服務(wù)器,每個(gè)機(jī)器需要啟動(dòng)100個(gè)apache或fpm工作進(jìn)程,那每個(gè)進(jìn)程都會(huì)產(chǎn)生一個(gè)長(zhǎng)連接到MySQL。這一共會(huì)產(chǎn)生1萬(wàn)個(gè)My SQL連接。大家都知道MySQL是每個(gè)連接會(huì)占用1個(gè)線程。那MYSQL就需要?jiǎng)?chuàng)建1萬(wàn)個(gè)線程,這樣大量的系統(tǒng)資源被浪費(fèi)在線程間上下文切換上。而你的業(yè)務(wù)代碼中并不是所有地方都在做數(shù)據(jù)庫(kù)操作,所以這個(gè)就是浪費(fèi)的。
連接池就不同了,100個(gè)worker進(jìn)程,公用10個(gè)數(shù)據(jù)庫(kù)連接即可,當(dāng)操作完數(shù)據(jù)庫(kù)后,立即釋放資源給其他worker進(jìn)程。這樣就算有100臺(tái)PHP的服務(wù)器,那也只會(huì)創(chuàng)建1000個(gè)MySQL的連接,完全可以接受的。
以前確實(shí)沒(méi)有好的辦法來(lái)解決此問(wèn)題的,現(xiàn)在有了swoole擴(kuò)展,利用swoole提供的task功能可以很方便做出一個(gè)連接池來(lái)。
代碼如下:
$serv = new swoole_server("127.0.0.1", 9508);
$serv->set(array(
'worker_num' => 100,
'task_worker_num' => 10, //MySQL連接的數(shù)量
));
function my_onReceive($serv, $fd, $from_id, $data)
{
//taskwait就是投遞一條任務(wù),這里直接傳遞SQL語(yǔ)句了
//然后阻塞等待SQL完成
$result = $serv->taskwait("show tables");
if ($result !== false) {
list($status, $db_res) = explode(':', $result, 2);
if ($status == 'OK') {
//數(shù)據(jù)庫(kù)操作成功了,執(zhí)行業(yè)務(wù)邏輯代碼,這里就自動(dòng)釋放掉MySQL連接的占用
$serv->send($fd, var_export(unserialize($db_res), true) . "\n");
} else {
$serv->send($fd, $db_res);
}
return;
} else {
$serv->send($fd, "Error. Task timeout\n");
}
}
function my_onTask($serv, $task_id, $from_id, $sql)
{
static $link = null;
if ($link == null) {
$link = mysqli_connect("127.0.0.1", "root", "root", "test");
if (!$link) {
$link = null;
$serv->finish("ER:" . mysqli_error($link));
return;
}
}
$result = $link->query($sql);
if (!$result) {
$serv->finish("ER:" . mysqli_error($link));
return;
}
$data = $result->fetch_all(MYSQLI_ASSOC);
$serv->finish("OK:" . serialize($data));
}
function my_onFinish($serv, $data)
{
echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL;
}
$serv->on('Receive', 'my_onReceive');
$serv->on('Task', 'my_onTask');
$serv->on('Finish', 'my_onFinish');
$serv->start();
看完上面的,看完了,覺(jué)得真的很厲害。畢竟,現(xiàn)在來(lái)說(shuō)可能是還沒(méi)有真正在PHP使用數(shù)據(jù)庫(kù)連接池的大應(yīng)用。今天終于準(zhǔn)備實(shí)驗(yàn)實(shí)驗(yàn)。
在開(kāi)始之前,還是說(shuō)一下測(cè)試環(huán)境吧:OS CentOS 6.4 x86;php 5.3.17;MySQL 5.5.28;Swoole 1.6.8。
首先,我們當(dāng)然要把代碼寫好。下面是服務(wù)端代碼,很多是參考了上面文章里面的,當(dāng)然也有自己的內(nèi)容。
$serv = swoole_server_create('127.0.0.1', 3305, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);//端口3305
swoole_server_set($serv, array(
'worker_num' => 2,??????//worker線程的數(shù)量
'task_worker_num' => 1, //MySQL連接的數(shù)量
));//作為小型測(cè)試,參數(shù)調(diào)得比較小
//這里有一個(gè)守護(hù)進(jìn)程化的參數(shù),由于是實(shí)驗(yàn),沒(méi)有加入
function my_onReceive($serv, $fd, $from_id, $data){
//執(zhí)行查詢
$result = $serv->taskwait($data);
if ($result !== false) {
swoole_server_send($serv, $fd, $result);
return;
} else {
swoole_server_send($serv, $fd, "Error. Task timeout\n");
}
}
function my_onTask($serv, $task_id, $from_id, $sql){
static $link = NULL;
if ($link == NULL) {
$link = mysqli_connect('localhost', 'user', 'pw', 'db');
//localhost=>UNIX Socket , IP地址=>TCP/IP
}
$result = $link->query($sql);
if ($result === false) {
swoole_server_finish($serv, 'b:0;');//語(yǔ)句運(yùn)行失敗,這是serialize后的false,下同理
return;
}
if ($result === true){
swoole_server_finish($serv, 'b:1;');//寫入操作成功
return;
}
$data = $result->fetch_all(MYSQLI_ASSOC);
swoole_server_finish($serv, serialize($data));
}
function my_onFinish($serv, $data){
//這次實(shí)驗(yàn)就沒(méi)有寫東西了
//但是必須有這個(gè)函數(shù)定義
//其實(shí)可以寫日志什么的吧
}
swoole_server_handler($serv, 'onReceive', 'my_onReceive');
swoole_server_handler($serv, 'onTask', 'my_onTask');
swoole_server_handler($serv, 'onFinish', 'my_onFinish');
//上面是設(shè)置回調(diào)函數(shù)
swoole_server_start($serv);
swoole_event_wait();//實(shí)驗(yàn)環(huán)境是PHP5.3,所以需要這個(gè)函數(shù)進(jìn)行事件輪詢;5.4+就不需要了
如果把上面的連接池代碼和Rango的相比較,會(huì)發(fā)現(xiàn)我的對(duì)于錯(cuò)誤的部分處理很少。其實(shí)這個(gè)時(shí)候用CLI運(yùn)行這個(gè)連接池,然后使用Telnet已經(jīng)就可以直接測(cè)試效果了。但是不管怎么說(shuō)這個(gè)東西還是要應(yīng)用在PHP上面的,我簡(jiǎn)單地寫一下PHP方面的代碼。
$link=new swoole_client(SWOOLE_SOCK_TCP,SWOOLE_SOCK_SYNC);//TCP方式、同步
$link->connect('127.0.0.1',3305);//連接
$link->send('SELECT * FROM `table`');//執(zhí)行查詢
$res=unserialize($link->recv());
if(!res){
echo 'Failed!';
}
else{
print_r($res);
}
$link->close();
//上面的是最簡(jiǎn)單的測(cè)試,下面可以簡(jiǎn)單地改寫成函數(shù)
function dbcp_query($sql){
$link=new swoole_client(SWOOLE_SOCK_TCP,SWOOLE_SOCK_SYNC);//TCP方式、同步
$link->connect('127.0.0.1',3305);//連接
$link->send($sql);//執(zhí)行查詢
return unserialize($link->recv());
//swoole_client類析構(gòu)時(shí)會(huì)自動(dòng)關(guān)閉連接
}
現(xiàn)在可以運(yùn)行了,本次實(shí)驗(yàn)是成功的。但是如果使用dbcp_query()這個(gè)函數(shù),每次調(diào)用都要發(fā)起一次TCP連接,執(zhí)行的語(yǔ)句多了,肯定出問(wèn)題。這個(gè)時(shí)候我們就可以把它封裝成一個(gè)類了,單純實(shí)現(xiàn)這個(gè)會(huì)比較的簡(jiǎn)單,但是打出來(lái)要點(diǎn)時(shí)間,這里就不寫了。
最后:今天做的是數(shù)據(jù)庫(kù)連接池的實(shí)現(xiàn)。從上面的代碼我們可以看見(jiàn),程序與連接池之間的數(shù)據(jù)交換是使用php序列進(jìn)行的。這里會(huì)有兩次的serialize、unserialize,絕對(duì)也是一個(gè)開(kāi)銷。Rango的文章里面有說(shuō)到“MySQL是每個(gè)連接會(huì)占用1個(gè)線程……大量的系統(tǒng)資源被浪費(fèi)在線程間上下文切換上……不是所有地方都在做數(shù)據(jù)庫(kù)操作,所以這個(gè)就是浪費(fèi)的。”再看看他那篇文章的假設(shè):“假設(shè)有100臺(tái)PHP的應(yīng)用服務(wù)器,每個(gè)機(jī)器需要啟動(dòng)100個(gè)apache或fpm工作進(jìn)程?!边@肯定不是一個(gè)小項(xiàng)目,確實(shí)就適合用連接池了。寫的東西是用來(lái)練手或者解悶兒的?常規(guī)方法已經(jīng)可以了。不要忘了一點(diǎn):程序與連接池的交互我們應(yīng)該還是用Swoole實(shí)現(xiàn)的,Swoole可是一個(gè)TCP/UDP擴(kuò)展。而Swoole只能運(yùn)行在Linux平臺(tái)上面,但是Linux平臺(tái)上的MySQL是可以用UNIX Socket通訊的。
P.S.:找個(gè)時(shí)間給epdb改寫一個(gè)支持?jǐn)?shù)據(jù)庫(kù)連接池的版本。
來(lái)自:http://rango.swoole.com/archives/265
http://bokjan.com/prog/php-db-conn-pool-with-swoole.html
總結(jié)
以上是生活随笔為你收集整理的php cli swoole mysql_[了解实践]Swoole、PHP与MySQL:连接池,swoole扩展实现真正的PHP数据库连接池。...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python色标_在Python中用色标
- 下一篇: 查看mysql数据库历史_查看mysql