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