MySQL 连接池


swoole实现 MySQL连接池 降低数据库连接数 降低IO消耗
网站开发中 LNMP模式 由Nginx的master进程接收请求 分给多个worker进程 每个worker进程 链接php-fpm的master进程
php-fpm 根据当前情况 调用其worker进程 然后处理PHP
如果需要MySQL 与MySQL建立连接 如有10000个请求  需要与MySQL建立10000个连接 MySQL是每个连接会 用1个线程  MYSQL就 要创建1万个线程
系统资源 被浪费在 线程间 上下文切换上
业务 不是 所有地方 在做数据库操作 所以 浪费  数据库的压力 非常大

连接池技术
100个worker进程 公用10个 数据库连接 即可  操作完数据库后 释放资源给其他worker进程  就算10000个请求  也只 创建1000个MySQL的连接 可以接受
swoole提供 task功能 很方便做 连接池

为什么能降低IO消耗
MySQL短连接 每次请求 数据库 要建立与MySQL服务器建立TCP连接 时间开销 TCP连接需要3次网络通信  延时和额外的IO消耗
请求结束后 关闭MySQL连接 还会发生3/4次网络通信
连接池 采用 MySQL 长连接模式  保持与MySQL 连接 重用连接进行MySQL的操作 节省 建立连接和断开连接的消耗

为什么能降低数据连接数呢?
连接池  维持若干 长连接  新请求到达时 如果连接池空闲 就分配给 连接池去处理 否则 后面的数据库连接请求 被加入到等待队列中
swoole实现 MySQL连接池 DBserver_task.php
<?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

客户端 做测试DBclient.php
<?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
# php DBserver_task.php
后运行client
# 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端的输出结果
# 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
$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
$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]
可以看出用了连接池 效果 很明显

断线重连
造成这样的原因一般是sql操作的时间过长 或者是传送的数据太大
mysql 有 个 wait_timeout参数 默认设置为8个小时 当超过8个小时没有数据交互时 mysql服务器会主动关闭掉超时的连接 对应的mysql 错误码是2006 报错为MySQL server has gone away
当查询的结果集超过 max_allowed_packet 也会出现这样的报错
错误:2013 (CR_SERVER_LOST)
消息:查询过程中丢失了与MySQL服务器的连接