基於swoole的mysql連線池實現

lookyourface發表於2020-12-16

前言

傳統的nginx+FPM模式的PHP程式而言,每次請求FPM的worker都會連線一次mysql,然後請求結束便會斷開連線。對於併發小的應用來說這不會有什麼問題,但是對於高併發的應用來說,頻繁建立連線Connect和銷燬連線Close,資料庫便會成為瓶頸,相信不少人也遇到過to many connection的mysql報錯吧。

連線池的優勢

連線池採用的是長連線模式,會一直保持與MySQL的連線,用完後會重新放回連線池,從而節省了建立連線和斷開連線的消耗,大大降低了系統IO的消耗,一定程度上提高了程式的併發效能。如果連線池空閒,就從連線池分配一個連線,否則,請求將被加入到等待佇列中。

實現

我們採用swoole實現mysql連線池

連線池類
<?php

require_once "MysqlDB.php";

class MysqlPool
{
    private static $instance;
    private $pool;
    private $config;
    private $pool_get_timeout;

    /**
     * 獲取mysql程式池單例
     * @param null $config
     * @return MysqlPool
     */
    public static function getInstance($config = null)
    {
        if (empty(self::$instance)) {
            if (empty($config)) {
                throw new RuntimeException("mysql config is empty");
            }
            self::$instance = new static($config);
        }
        return self::$instance;
    }

    public function __construct($config)
    {
        if (empty($this->pool)) {
            $this->config = $config;
            $this->pool = new \Swoole\Coroutine\Channel($config['pool_size']);
            for ($i = 0; $i < $config['pool_size']; $i++) {
                \go(function() use ($config) {
                    $mysql = new MysqlDB();
                    $res = $mysql->connect($config['mysql']);
                    if ($res === false) {
                        throw new RuntimeException("Failed to connect mysql server");
                    } else {
                        $this->pool->push($mysql);
                    }
                });
            }
        }
    }

    public function get()
    {
        if ($this->pool->length() > 0) {
            $mysql = $this->pool->pop($this->config['pool_get_timeout']);
            if (false === $mysql) {
                throw new RuntimeException("Pop mysql timeout");
            }
            return $mysql;
        } else {
            throw new RuntimeException("Pool length <= 0");
        }
    }

    public function recycle(MysqlDB $mysql){
        $this->pool->push($mysql);
    }

    /**
     * 獲取連線池長度
     * @return mixed
     */
    public function getPoolSize(){
        return $this->pool->length();
    }
}
資料庫DB類
<?php

class MysqlDB
{
    private $connection;

    public function connect($config)
    {
        $connection = new \Swoole\Coroutine\MySQL();
        $res = $connection->connect($config);
        if ($res === false) {
            throw new RuntimeException($connection->connect_error, $connection->errno);
        } else {
            $this->connection = $connection;
        }
        return $res;
    }


    public function query($sql){
        $result = $this->connection->query($sql);
        return $result;
    }
}
在HTTP協程伺服器中建立連線池
<?php
require_once "MysqlPool.php";
\Co\run(function () {
    $server = new \Co\Http\Server("0.0.0.0", 9501, false);
    $pool = MysqlPool::getInstance([
        'pool_size'=>5,
        'pool_get_timeout'=>1,
        'timeout'=>1,
        'charset'=>'utf8',
        'strict_type'=>false,
        'fetch_mode'=>true,
        'mysql'=>[
            'host'=>'127.0.0.1',
            'port'=>'3306',
            'user'=>'homestead',
            'password'=>'secret',
            'database'=>'blog',
        ]
    ]);
    $server->handle('/', function ($request, $response) use ($pool){
        $mysql = $pool->get();
        $res = $mysql->query("select id,phone,username from user limit 1");
        var_dump($res);
        $pool->recycle($mysql);
        $response->end("<h1>Test</h1>");
    });
    $server->handle('/test', function ($request, $response) {
        $response->end("<h1>Test</h1>");
    });
    $server->handle('/stop', function ($request, $response) use ($server) {
        $response->end("<h1>Stop</h1>");
        $server->shutdown();
    });
    $server->start();
});
原始碼地址 swoole-mysql-pool
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章