編程學習網 > 數據庫 > swoole channel之mysql連接池實現
2021
06-09

swoole channel之mysql連接池實現

本篇文章將利用channel來實現一個簡單的mysql連接池,并且介紹利用新的的特性:defer來實現資源的回收

為什么要實現mysql連接池?
有以下幾個原因:

  1. 保持長連接可以節省連接相關的開銷(不過由于swoole本身常駐進程,所以只要不手工close,也還是長連接了)

  2. mysql本身對連接有限制,所以每個請求(協程)都建立一個連接很容易導致mysql連接被打滿


下面就來看一段代碼,看swoole里實現一個連接池是怎么樣的簡單


<?php  use Swoole\Coroutine\MySQL; class MysqlPool { private static $instance; private $pool; //連接池容器,一個channel     private $config; /**      * @param null $config      * @return MysqlPool      * @desc 獲取連接池實例      */     public static function getInstance($config = null)
    { if (empty(self::$instance)) { if (empty($config)) { throw new RuntimeException("mysql config empty");
            } self::$instance = new static($config);
        } return self::$instance;
    } /**      * MysqlPool constructor.      * @param $config      * @desc 初始化,自動創建實例,需要放在workerstart中執行      */     public function __construct($config)
    { if (empty($this->pool)) {
            $this->config = $config;
            $this->pool = new chan($config['pool_size']); for ($i = 0; $i < $config['pool_size']; $i++) {
                $mysql = new MySQL();
                $res = $mysql->connect($config); if ($res == false) { //連接失敗,拋棄常                     throw new RuntimeException("failed to connect mysql server.");
                } else { //mysql連接存入channel                     $this->put($mysql);
                }
            }
        }
    } /**      * @param $mysql      * @desc 放入一個mysql連接入池      */     public function put($mysql)
    {
        $this->pool->push($mysql);
    } /**      * @return mixed      * @desc 獲取一個連接,當超時,返回一個異常      */     public function get()
    {
        $mysql = $this->pool->pop($this->config['pool_get_timeout']); if (false === $mysql) { throw new RuntimeException("get mysql timeout, all mysql connection is used");
        } return $mysql;
    } /**      * @return mixed      * @desc 獲取當時連接池可用對象      */     public function getLength()
    { return $this->pool->length();
    }

}


那如何使用呢?繼續看一段代碼:

<?php require_once "mysql_pool.php";
$config = [ 'host' => '127.0.0.1', //數據庫ip     'port' => 3306, //數據庫端口     'user' => 'root', //數據庫用戶名     'password' => '123456', //數據庫密碼     'database' => 'test', //默認數據庫名     'timeout' => 0.5, //數據庫連接超時時間     'charset' => 'utf8mb4', //默認字符集     'strict_type' => true, //ture,會自動表數字轉為int類型     'pool_size' => '3', //連接池大小     'pool_get_timeout' => 0.5, //當在此時間內未獲得到一個連接,會立即返回。(表示所以的連接都已在使用中) ]; //創建http server $http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->set([ //"daemonize" => true,     "worker_num" => 1, "log_level" => SWOOLE_LOG_ERROR,
]);

$http->on('WorkerStart', function ($serv, $worker_id) use ($config) { //worker啟動時,每個進程都初始化連接池,在onRequest中可以直接使用     try { MysqlPool::getInstance($config);
    } catch (\Exception $e) { //初始化異常,關閉服務         echo $e->getMessage() . PHP_EOL;
        $serv->shutdown();
    } catch (\Throwable $throwable) { //初始化異常,關閉服務         echo $throwable->getMessage() . PHP_EOL;
        $serv->shutdown();
    }
});

$http->on('request', function ($request, $response) { //瀏覽器會自動發起這個請求,這也是很多人碰到的一個問題:     //為什么我瀏覽器打開網站,收到了兩個請求?     if ($request->server['path_info'] == '/favicon.ico') {
        $response->end(''); return;
    } //獲取數據庫     if ($request->server['path_info'] == '/list') {

        go(function () use ($request, $response) { //從池子中獲取一個實例             try {
                $pool = MysqlPool::getInstance();
                $mysql = $pool->get();
                defer(function () use ($mysql) { //利用defer特性,可以達到協程執行完成,歸還$mysql到連接池                     //好處是 可能因為業務代碼很長,導致亂用或者忘記把資源歸還                     MysqlPool::getInstance()->put($mysql); echo "當前可用連接數:" . MysqlPool::getInstance()->getLength() . PHP_EOL;
                });
                $result = $mysql->query("select * from test");
                $response->end(json_encode($result));
            } catch (\Exception $e) {
                $response->end($e->getMessage());
            }
        }); return;
    } //模擬timeout, 瀏覽器打開4個tab,都請求 http://127.0.0.1:9501/timeout,前三個應該是等10秒出結果,第四個500ms后出超時結果     //ps: chrome瀏覽器,需要加一個隨機數,http://127.0.0.1:9501/timeout?t=0, http://127.0.0.1:9501/timeout?t=1, 因為chrome會對完全一樣的url做并發請求限制     echo "get request:".time().PHP_EOL; if ($request->server['path_info'] == '/timeout') {
        go(function () use ($request, $response) { //從池子中獲取一個實例             try {
                $pool = MysqlPool::getInstance(); echo "當前可用連接數:" . $pool->getLength() . PHP_EOL;
                $mysql = $pool->get(); echo "當前可用連接數:" . $pool->getLength() . PHP_EOL;
                defer(function () use ($mysql) { //協程執行完成,歸還$mysql到連接池                     MysqlPool::getInstance()->put($mysql); echo "當前可用連接數:" . MysqlPool::getInstance()->getLength() . PHP_EOL;
                });
                $result = $mysql->query("select * from test");
                \Swoole\Coroutine::sleep(10); //sleep 10秒,模擬耗時操作                 $response->end(json_encode($result));
            } catch (\Exception $e) {
                $response->end($e->getMessage());
            }
        }); return;
    }

});

$http->start();


幾個重點

  1. 在workerStart初始化連接,可以做一些前置判斷

  2. 利用defer特性,以免亂用或忘記歸還資源,減輕開發心智負擔


修改自己的數據配置,和執行語句,然后瀏覽器執行: 
http://127.0.0.1:9501/list  可以看到正常的結果輸出
http://127.0.0.1:9501/timeout 演示連接池取和存的過程,大家也可以實驗一下(注意看注釋里的說明)


為什么用channel? 

不用channel,直接用一個array或者sqlQueue也是可以的,用channel有幾個好處

  1. channel也是可被協程調度的

  2. channel->pop的時候,可以設置超時,用array 或者 sqlQueue 實現就會麻煩一些


需要注意的點

  1. 由于swoole是多進程架構,直連mysql的話,連接數=worker_num * pool_size

  2. defer需要swoole版本 >= 4.2.9

以上就是“swoole channel之mysql連接池實現”的詳細內容,想要了解更多關于swoole相關資訊或者知識歡迎關注編程學習網

掃碼二維碼 獲取免費視頻學習資料

Python編程學習

查 看2022高級編程視頻教程免費獲取