本篇文章將利用channel來實現一個簡單的mysql連接池,并且介紹利用新的的特性:defer來實現資源的回收
為什么要實現mysql連接池?
有以下幾個原因:
-
保持長連接可以節省連接相關的開銷(不過由于swoole本身常駐進程,所以只要不手工close,也還是長連接了)
-
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();
幾個重點
-
在workerStart初始化連接,可以做一些前置判斷
-
利用defer特性,以免亂用或忘記歸還資源,減輕開發心智負擔
修改自己的數據配置,和執行語句,然后瀏覽器執行:
http://127.0.0.1:9501/list 可以看到正常的結果輸出
http://127.0.0.1:9501/timeout 演示連接池取和存的過程,大家也可以實驗一下(注意看注釋里的說明)
為什么用channel?
不用channel,直接用一個array或者sqlQueue也是可以的,用channel有幾個好處
-
channel也是可被協程調度的
-
channel->pop的時候,可以設置超時,用array 或者 sqlQueue 實現就會麻煩一些
需要注意的點
-
由于swoole是多進程架構,直連mysql的話,連接數=worker_num * pool_size
-
defer需要swoole版本 >= 4.2.9
以上就是“swoole channel之mysql連接池實現”的詳細內容,想要了解更多關于swoole相關資訊或者知識歡迎關注編程學習網
掃碼二維碼 獲取免費視頻學習資料
- 本文固定鏈接: http://www.wangchenghua.com/post/7992/
- 轉載請注明:轉載必須在正文中標注并保留原文鏈接
- 掃碼: 掃上方二維碼獲取免費視頻資料