Swoole封裝MySQL與Redis的連接池

連接池

  1. 什么是連接池鞭莽?

    連接池是創(chuàng)建和管理一個連接的緩沖池的技術(shù)普气,緩沖池中的連接可以被任何需要他們的線程使用。

    一個服務(wù)端資源的連接數(shù)量是有限的柑船,比如:Redis的maxclients帽撑、MySQL的max_connections、PHP-FPM的max_children鞍时,start_servers 等配置參數(shù)亏拉,都是用來設(shè)置連接數(shù)的。

    連接池的原理就是在資源初始化時將一定數(shù)量的連接存放在連接池中逆巍,誰用誰取及塘,用完后再放回去,實現(xiàn)連接的復(fù)用蒸苇。如果超出連接池容量磷蛹,會進行排隊等待或者直接丟棄。

  2. 連接池的優(yōu)勢溪烤?

    • 減少連接創(chuàng)建時間:連接池中的連接是已經(jīng)準(zhǔn)備好的味咳、可重復(fù)使用的
    • 簡化的編程模式:當(dāng)使用連接池時,每一個單獨的線程能夠像創(chuàng)建一個自己的PDO連接一樣操作
    • 控制資源的使用:增強系統(tǒng)的穩(wěn)定性檬嘀,將資源利用控制在一定的水平之下
  3. 為什么使用連接池槽驶?

    其實通過它的優(yōu)勢就能明白我們?yōu)槭裁匆褂眠B接池。連接池最大的作用并不是在于對性能提升多少鸳兽,而是保護服務(wù)資源掂铐。比如MySQL的最大連接數(shù)是100,但是項目在某一時刻突然涌入1000個請求揍异,MySQL承載不了這么多的連接數(shù)全陨,很可能會崩潰,進而影響到整個項目的訪問衷掷。如果我們使用了連接池辱姨,在池中放入64個MySQL連接,那不管是多少請求過來戚嗅,MySQL最多只會被占用64個連接雨涛,從而保證MySQL的安全穩(wěn)定,而不會被大流量瞬間擊垮懦胞。

  4. 連接池里的連接個數(shù)設(shè)置多少合適替久?

    連接池里的連接個數(shù)設(shè)置一般不低于服務(wù)資源最大連接個數(shù)的三分之一,不超過三分之二躏尉。比如:MySQL設(shè)置的最大連接數(shù)(max_connections)為100蚯根,那連接池個數(shù)在50-70之間是比較合理的,設(shè)置太小沒意義胀糜,太大可能會有一些不可控的問題導(dǎo)致連接超出颅拦。至于MySQL的最大連接數(shù)(max_connections)如何設(shè)置這個就得看機器了吼具,服務(wù)器的CPU、內(nèi)存等硬件配置都會影響服務(wù)資源的最大連接個數(shù)矩距。

封裝前的說明

本次我是在Laravel框架中借助Swoole協(xié)程來實現(xiàn)對Redis及MySQL連接池的封裝,Swoole官方文檔中也給出了使用示例怖竭,可以參考:https://wiki.swoole.com/#/coroutine/conn_pool

封裝前提是已經(jīng)安裝好了PHP的swoole及redis的擴展

本次使用版本介紹:Laravel版本是7+锥债,Swoole版本是4+,Redis版本是6+

封裝MySQL連接池

  1. 在Laravel的app目錄下創(chuàng)建文件夾Pool用于放置所有代碼痊臭,然后在Pool目錄下再創(chuàng)建文件夾Core用于放置核心類哮肚,在Core目錄下創(chuàng)建類文件MySQL.php,內(nèi)容如下

    <?php
    
    namespace App\Pool\Core;
    
    
    class MySQL
    {
        /**
         * 在實例化時獲取到的連接池對象
         * @var
         */
        protected $pool;
    
        public function __construct($pool)
        {
            $this->pool = $pool;
        }
    
        /**
         * 從連接池中獲取連接
         * @return mixed
         */
        public function connection()
        {
            return $this->pool->get();
        }
    
        /**
         * 向連接池中歸還連接
         * @param $pdo
         */
        public function put($pdo)
        {
            $this->pool->put($pdo);
        }
    
        /**
         * MySQL查詢操作
         * @param $sql
         * @return string
         */
        public function query($sql)
        {
            try {
                $pdo = $this->connection();
                $return = $pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
                // 歸還連接广匙,很重要T侍恕!鸦致!
                $this->put($pdo);
    
                return $return;
            } catch (\PDOException $e) {
                // 出現(xiàn)異常潮剪,歸還一個空連接以保證連接池的數(shù)量平衡。很重要7滞佟?古觥!
                $this->put(null);
                return $e->getMessage();
            }
        }
    
        /**
         * MySQL增刪改操作
         * @param $sql
         * @return bool|string
         */
        public function execute($sql)
        {
            try {
                $pdo = $this->connection();
                $pdo->exec($sql);
                $this->put($pdo);
    
                return true;
            } catch (\PDOException $e) {
                $this->put(null);
                return $e->getMessage();
            }
        }
    }
    
  2. 在Pool目錄下再創(chuàng)建Database文件夾绽乔,存放封裝的MySQL連接池弧蝇,在下邊創(chuàng)建類 DbPool.php,內(nèi)容如下

    <?php
    
    namespace App\Pool\Database;
    
    
    use Illuminate\Foundation\Application;
    use Swoole\Database\PDOConfig;
    use Swoole\Database\PDOPool;
    
    class DbPool
    {
        protected $app;
    
        protected $pool;
    
        public function __construct(Application $app)
        {
            $this->app = $app;
            $this->init();
        }
    
        /**
         * 創(chuàng)建連接池折砸,如果需要連接多個庫看疗,將$config換成數(shù)組形式,下邊在foreach中創(chuàng)建
         * .env文件中需要配置好下邊的一些參數(shù)睦授,DB_SIZE是連接池中連接的數(shù)量
         */
        protected function init()
        {
            $config = (new PDOConfig())
                ->withHost(env('DB_HOST'))
                ->withPort(env('DB_PORT'))
                ->withDbname(env('DB_DATABASE'))
                ->withUsername(env('DB_USERNAME'))
                ->withPassword(env('DB_PASSWORD'));
    
            $this->pool = new PDOPool($config, env('DB_SIZE'));
        }
    
        public function get()
        {
            return $this->pool->get();
        }
    
        public function put($pdo)
        {
            $this->pool->put($pdo);
        }
    }
    
  3. 在Pool目錄下創(chuàng)建Db類

    <?php
    
    namespace App\Pool;
    
    
    use Swoole\Coroutine;
    use Swoole\Coroutine\Channel;
    use Swoole\Runtime;
    
    class Db
    {
        /**
         * 獲取驅(qū)動
         */
        public static function getDriver()
        {
            return app('mysql_pool');
        }
    
        /**
         * @param $name
         * @param $arguments
         * @return mixed
         */
        public static function __callStatic($name, $arguments)
        {
            // 開啟一鍵協(xié)程化
            Runtime::enableCoroutine();
            // 實例化通道两芳,用于協(xié)程間通訊
            $channel = new Channel(1);
            Coroutine::create(function () use ($channel, $name, $arguments) {
                $pdo = self::getDriver();
                $return = $pdo->$name(...$arguments);
                $channel->push($return);
            });
    
            return $channel->pop();
        }
    }
    
  4. 在服務(wù)提供者 App/Providers/AppServiceProvider.php 的 boot 方法中注冊單例

    use App\Pool\Core\MySQL;
    use App\Pool\Database\DbPool;
    ...
    public function boot()
    {
        $this->app->singleton('mysql_pool', function () {
            return new MySQL(new DbPool($this->app));
        });
    }
    
  5. 創(chuàng)建一個控制器,測試MySQL連接池(使用協(xié)程模擬并發(fā)100訪問查詢)

     use App\Pool\Db;
     use Swoole\Coroutine;
     use Swoole\Coroutine\Channel;
     ...
     public function testMySQLPool()
     {
         try {
             $chan = new Channel(1);
             for ($i = 0; $i < 100; $i++) {
                 Coroutine::create(
                     function () use ($chan) {
                         $chan->push(Db::query("select * from test"));
                     }
                 );
             }
             return $chan->pop();
         } catch (\Exception $e) {
             return 111;
         }
     }
    

    訪問此方法睹逃,然后在數(shù)據(jù)庫中用 show processlist; 命令查看當(dāng)前數(shù)據(jù)庫的連接數(shù)盗扇,對比連接數(shù)和設(shè)置的連接池中連接數(shù)量,驗證連接池是否有效沉填。MySQL內(nèi)部默認(rèn)會建立幾個連接疗隶,這幾個不用管。

封裝Redis連接池

  1. 還是在Pool/Core目錄下創(chuàng)建Redis的核心類 CoRedis.php

    RedisCommand 文件放的就是Redis的一些操作命令翼闹,比如:set()斑鼻,get() 等。由于文件內(nèi)容太長就不貼出來了猎荠,放個網(wǎng)盤鏈接坚弱,自己下載看吧

    鏈接:https://pan.baidu.com/s/1eN5OjX-QPzYhlXcOgLGsHw
    提取碼:udil

    <?php
    
    namespace App\Pool\Core;
    
    class CoRedis
    {
        protected $pool;
        
        public function __construct($pool)
        {
            $this->pool = $pool;
        }
    
        public function connection()
        {
            return $this->pool->get();
        }
    
        public function put($redis)
        {
            $this->pool->put($redis);
        }
    
        public function __call($name, $arguments)
        {
            try {
                $redisConnection = $this->connection();
                $redis = new RedisCommand($redisConnection);
                $return = $redis->$name(...$arguments);
                $this->put($redisConnection);
                return $return;
            } catch (\Exception $e) {
                $this->put(null);
                return $e->getMessage();
            }
        }
    }
    
  2. 在Pool目錄下創(chuàng)建Redis文件夾(還放在上邊創(chuàng)建的Database目錄下也行)蜀备,創(chuàng)建Redis連接池類 RedisPool.php

    <?php
    
    namespace App\Pool\Redis;
    
    
    use Illuminate\Foundation\Application;
    use Swoole\Database\RedisConfig;
    use Swoole\Database\RedisPool as Pool;
    
    class RedisPool
    {
        protected $app;
    
        protected $pool;
    
        public function __construct(Application $app)
        {
            $this->app = $app;
            $this->init();
        }
    
        protected function init()
        {
            $config = (new RedisConfig)
                ->withHost(env('REDIS_HOST'))
                ->withPort(env('REDIS_PORT'))
                ->withAuth(env('REDIS_PASSWORD'));
    
            $this->pool = new Pool($config, env('REDIS_SIZE'));
        }
    
        public function get()
        {
            return $this->pool->get();
        }
    
        public function put($pdo)
        {
            $this->pool->put($pdo);
        }
    }
    
  3. 在Pool目錄下創(chuàng)建 Redis.php

    <?php
    
    namespace App\Pool;
    
    
    use Swoole\Coroutine;
    use Swoole\Coroutine\Channel;
    use Swoole\Runtime;
    
    class Redis
    {
        public static function getDriver()
        {
            return app('redis_pool');
        }
    
        public static function __callStatic($name, $arguments)
        {
            Runtime::enableCoroutine();
            $channel = new Channel(1);
            Coroutine::create(function () use ($channel, $name, $arguments) {
                $redis = self::getDriver();
                $return = $redis->$name(...$arguments);
                $channel->push($return);
            });
    
            return $channel->pop();
        }
    }
    
  4. 在服務(wù)提供者 App/Providers/AppServiceProvider.php 的 boot 方法中注冊單例

    use App\Pool\Core\CoRedis;
    use App\Pool\Redis\RedisPool;
    ...
    public function boot()
    {
        ...
        $this->app->singleton('redis_pool', function () {
             return new CoRedis(new RedisPool($this->app));
         });
    }
    
  5. 在控制器中測試

    use App\Pool\Redis;
    ...
    public function test_redis()
    {
         try {
             Redis::set('k1', 1111);
             $chan = new Channel(1);
             for ($i = 0; $i < 100; $i++) {
                 Coroutine::create(
                     function () use ($chan) {
                         $chan->push(Redis::get('k1'));
                     }
                 );
             }
             return $chan->pop();
         } catch (\Exception $e) {
             return 111;
         }
    }
    

    測試過程與MySQL一致,在Redis客戶端中使用 info clients 命令來查看當(dāng)前連接數(shù)

最終的目錄結(jié)構(gòu)展示

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荒叶,一起剝皮案震驚了整個濱河市碾阁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌些楣,老刑警劉巖脂凶,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異愁茁,居然都是意外死亡蚕钦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門鹅很,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘶居,“玉大人,你說我怎么就攤上這事促煮∮势ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵菠齿,是天一觀的道長樱报。 經(jīng)常有香客問我泞当,道長迹蛤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任襟士,我火速辦了婚禮盗飒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陋桂。我一直安慰自己逆趣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布嗜历。 她就那樣靜靜地躺著宣渗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梨州。 梳的紋絲不亂的頭發(fā)上痕囱,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音暴匠,去河邊找鬼鞍恢。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的帮掉。 我是一名探鬼主播弦悉,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蟆炊!你這毒婦竟也來了稽莉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤涩搓,失蹤者是張志新(化名)和其女友劉穎肩祥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缩膝,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年岸霹,在試婚紗的時候發(fā)現(xiàn)自己被綠了疾层。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡贡避,死狀恐怖痛黎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刮吧,我是刑警寧澤湖饱,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站杀捻,受9級特大地震影響井厌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜致讥,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一仅仆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垢袱,春花似錦墓拜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爽锥,卻和暖如春涌韩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氯夷。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工贸辈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓擎淤,卻偏偏與公主長得像奢啥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘴拢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容