轉 Swoole】用swoole簡單實現MySQL連線池
MySQL連線池
在傳統的網站開發中,比如LNMP模式,由Nginx的master程序接收請求然後分給多個worker程序,每個worker程序再連結php-fpm的master程序,php-fpm再根據當前情況去呼叫其worker程序然後處理PHP,如果需要MySQL,在與MySQL建立連線,這個時候,如果有1000個請求打過來,就需要與MySQL建立1000個連線。如果請求達到萬級或者百萬級,這時資料庫的壓力就會非常大了。連線池技術就可以派上用場了,可以大大降低資料庫連線數,還可以降低IO消耗。
為什麼可以降低IO消耗?
MySQL短連線每次請求操作資料庫都需要建立與MySQL伺服器建立TCP連線,這是需要時間開銷的。TCP連線需要3次網路通訊。這樣就增加了一定的延時和額外的IO消耗。請求結束後會關閉MySQL連線,還會發生3/4次網路通訊。
而連線池採用的是MySQL的長連線模式,會一直保持與MySQL的連線,重用連線進行MySQL的操作,從而節省了建立連線和斷開連線的消耗。
為什麼可以降低資料連線數呢?
資料的連線池為維持若干個長連線,當新請求到達的時候,如果連線池空閒,就分配給連線池去處理,否則,後面的資料庫連線請求將被加入到等待佇列中。
用swoole實現一個簡單的MySQL連線池
<?php
$serv = new swoole_server('0.0.0.0', 9509);
$serv->set(array(
'worker_num' => 50, //worker程序數量
'task_worker_num' => 10, //task程序數量 即為維持的MySQL連線的數量
));
function my_onReceive($serv, $fd, $from_id, $data)
{
echo "收到資料".$data.PHP_EOL;
//taskwait就是投遞一條任務,這裡直接傳遞SQL語句了
//然後阻塞等待SQL完成,並返回結果
$result = $serv->taskwait($data);
echo "任務結束".PHP_EOL;
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");//如果返回的是false,則說明taskwait等待超時,可以設定相應的等待超時時間
}
}
function my_onTask($serv, $task_id, $from_id, $sql)
{
echo "開始做任務 task id:".$task_id.PHP_EOL;
static $link = null;
HELL:
if ($link == null) {
$link = @mysqli_connect("127.0.0.1", "root", "passwd", "database");
if (!$link) {
$link = null;
$serv->finish("ER:" . mysqli_error($link));
return;
}
}
$result = $link->query($sql);
if (!$result) { //如果查詢失敗了
if(in_array(mysqli_errno($link), [2013, 2006])){//錯誤碼為2013,或者2006,則重連資料庫,重新執行sql
$link = null;
goto HELL;
}else{
$serv->finish("ER:" . mysqli_error($link));
return;
}
}
if(preg_match("/^select/i", $sql)){//如果是select操作,就返回關聯陣列
$data = $result->fetch_assoc();
}else{//否則直接返回結果
$data = $result;
}
$serv->finish("OK:" . serialize($data));//呼叫finish方法,用於在task程序中通知worker程序,投遞的任務已完成
//return "OK:".serialize($data);
}
function my_onFinish($serv, $task_id, $data)
{
echo "任務完成";//taskwait 沒有觸發這個函式。。
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();//啟動server
這裡就不再說程式碼了,註釋很清楚了,如果對swoole函式有什麼疑問,請自行檢視官方文件。
接下來,寫了個客戶端程式碼來做測試
<?php
$client = new swoole_client(SWOOLE_SOCK_TCP);//建立swoole tcp客戶端
$client->connect('*.*.*.*', 9509, 10) or die("連線失敗");//連線server
while(true){
echo "請輸出要執行的sql: ";
$sql = trim(fgets(STDIN));
if($sql=='exit'){
break;
}
$client->send($sql);//傳送要執行的sql
$data = $client->recv();//阻塞接受返回的結果
var_dump($data);//列印
}
$client->close();//關閉連線
執行,首先啟動server
[root@VM_114_93_centos swoole]# php DBserver_task.php
然後執行client
[root@VM_114_93_centos swoole]# php DBclient.php
請輸出要執行的sql: select * from s_lvs_vip;
string(103) "array (
0 =>
array (
'id' => '3',
'protocol' => 'tcp',
'lb_algo' => 'ip_hash',
),
)
"
請輸出要執行的sql: insert into s_lvs_vip set protocol='tcp',lb_algo='ip_hash';
string(5) "true
"
請輸出要執行的sql: select * from s_lvs_vip;
string(196) "array (
0 =>
array (
'id' => '3',
'protocol' => 'tcp',
'lb_algo' => 'ip_hash',
),
1 =>
array (
'id' => '4',
'protocol' => 'tcp',
'lb_algo' => 'ip_hash',
),
)
"
請輸出要執行的sql: delete from s_lvs_vip where id=3;
string(5) "true
"
請輸出要執行的sql: select * from s_lvs_vip;
string(103) "array (
0 =>
array (
'id' => '4',
'protocol' => 'tcp',
'lb_algo' => 'ip_hash',
),
)
"
請輸出要執行的sql: exit
在看server端的輸出結果:
[root@VM_114_93_centos swoole]# php DBserver_task.php
收到資料select * from s_lvs_vip;
開始做任務 task id:0
任務結束
收到資料insert into s_lvs_vip set protocol='tcp',lb_algo='ip_hash';
開始做任務 task id:1
任務結束
收到資料select * from s_lvs_vip;
開始做任務 task id:2
任務結束
收到資料delete from s_lvs_vip where id=3;
開始做任務 task id:3
任務結束
收到資料select * from s_lvs_vip;
開始做任務 task id:4
任務結束
效能測試
同樣執行一條查詢
不用連線池的PHP程式碼
<?php
$conn = @mysqli_connect("***.***.***.***","root","905407339",'hulk');
if($conn){
//mysqli_select_db($conn,"hulk");
$res=mysqli_query($conn,'select * from s_lvs_vip');
$row=mysqli_fetch_assoc($res);
var_dump($row);
}else{
echo "ERROR";
}
用連線池的PHP程式碼
<?php
$sql = 'select * from s_lvs_vip';
$client = new swoole_client(SWOOLE_SOCK_TCP);
$client->connect('***.***.***.***', 9509, 10) or die("連線失敗");
$client->send($sql);
$data = $client->recv();
var_dump($data);
$client->close();
都用2000併發去測試多次,取平均值
ab -n 2000 -c 100 http://***.***.***.***/swoole/mysqli.php
ab -n 2000 -c 100 http://***.***.***.***/swoole/DBclient.php
型別 | 未使用連線池 | 使用了連線池 |
---|---|---|
總耗時 | 34.563 seconds | 25.407 seconds |
總請求量 | 2000 | 2000 |
併發量 | 100 | 100 |
Failed requests | 4 | 0 |
每秒鐘請求量 | 57.87 [#/sec] (mean) | 78.72 [#/sec] (mean) |
客戶端平均請求等待時間 | 1728.160 [ms] (mean) | 1270.370 [ms] (mean) |
服務端平均請求響應時間 | 17.282 [ms] | 12.704 [ms] |
通過這些測試資料,可以看出用了連線池以後,效果還是很明顯的,有一個質的改變。
注 : 博主用的是騰訊雲 1核 1M 1G記憶體的雲主機,測試資料僅供參考。
補充
小知識補充
斷線重連
造成這樣的原因一般是sql操作的時間過長,或者是傳送的資料太大。
在mysql中有一個wait_timeout引數,預設設定為8個小時,當超過8個小時沒有資料互動時,mysql伺服器會主動關閉掉超時的連線,對應的mysql 錯誤碼是2006,報錯為MySQL server has gone away
,當查詢的結果集超過 max_allowed_packet
也會出現這樣的報錯
錯誤:2013 (CR_SERVER_LOST)
訊息:查詢過程中丟失了與MySQL伺服器的連線。
關於以上兩個錯誤碼的詳細介紹,參照 http://blog.csdn.net/djvc/article/details/31379155。
https://blog.csdn.net/qq_28602957/article/details/78305039