PHP Swoole之mysql數(shù)據(jù)庫連接池的實(shí)現(xiàn)

最近一直在學(xué)習(xí)研究swoole,發(fā)現(xiàn)這個框架真是PHP的神作,swoole + php7 性能可以說是能與java媲美了,但是JAVA在處理高并發(fā)時還是比PHP更有優(yōu)勢,最近一直在想PHP是不是能像JAVA一樣實(shí)現(xiàn)數(shù)據(jù)庫連接池。

一般的fpm PHP應(yīng)用程序都是使用的數(shù)據(jù)庫短連接,每個php-fpm的請求都會新建一個mysql數(shù)據(jù)庫連接,那么在并發(fā)數(shù)很高的情況mysql的連接數(shù)很快就會用完,從而導(dǎo)致導(dǎo)致MySQL服務(wù)器崩潰。即使使用了mysql pconnet 但是又會導(dǎo)致很多無用的sleep連接。如何才能有效的避免這個問題 讓PHP也能開發(fā)像java一樣的高并發(fā)應(yīng)用呢。

連接池是可以有效降低MySQL-Server負(fù)載的。原理是 連接池使用一個共享資源的模式,如并發(fā)100個請求,實(shí)際上并不是每個請求的所有時間都在執(zhí)行SQL查詢。這樣100個請求,共享20個MySQL連接就可以滿足需求了。當(dāng)一個請求操作完數(shù)據(jù)庫后,開始進(jìn)入模板渲染等其它邏輯流程,這時就會釋放數(shù)據(jù)庫連接給其他的請求使用。

連接池僅在超大型應(yīng)用中才有價(jià)值。普通的應(yīng)用采用MySQL長連接方案,每個php-fpm創(chuàng)建一個MySQL連接,每臺機(jī)器開啟100個php-fpm進(jìn)程。如果有10臺機(jī)器,每臺機(jī)器并發(fā)的請求為100。實(shí)際上只需要創(chuàng)建1000個MySQL連接就能滿足需求,數(shù)據(jù)庫的壓力并不大。即使有100臺機(jī)器,硬件配置好的存儲服務(wù)器依然可以承受。

達(dá)到數(shù)百或者數(shù)千臺應(yīng)用服務(wù)器時,MySQL服務(wù)器就需要維持十萬級的連接。這時數(shù)據(jù)庫的壓力就會非常大了。連接池技術(shù)就可以派上用場了,可以大大降低數(shù)據(jù)庫連接數(shù)。

下面是我基于swoole擴(kuò)展實(shí)現(xiàn)的一個mysql數(shù)據(jù)庫連接池類,其基本原理是使用mysql長連接,使用空閑隊(duì)列分配數(shù)據(jù)庫連接,當(dāng)沒有空閑資源時生成新的數(shù)據(jù)庫連接,設(shè)置一個最大連接數(shù),和一個最小連接數(shù),當(dāng)超過設(shè)置的最大連接數(shù),不予申請新的數(shù)據(jù)庫連接,當(dāng)sleep的連接數(shù)超過最小連接數(shù)時,釋放多余的mysql連接。并且程序中也實(shí)現(xiàn)了mysql長連接的斷線重連,下面給出連接池程序源碼歡迎同學(xué)們吐槽試用,報(bào)bug。

<?php
namespace frame\base;

use frame\log\Log;

class MysqlPool
{
    const MAX_CONN = 100;
    const TIME_OUT = 1800;
    const MIN_CONN = 10; //最小連接
    const RECOVERY_TIME_INTERVAL = 30000; //定時回收毫秒

    public static $working_pool; //工作連接池
    public static $free_queue; //空閑連接資源隊(duì)列
    public static $close_queue; //已關(guān)閉連接資源隊(duì)列
    public static $config;
    public static $timer_start = false;

    /**
     * [init 連接池初始化 支持多個數(shù)據(jù)庫連接池]
     * @param  $connkey 連接關(guān)鍵字,可以實(shí)現(xiàn)多個不同的數(shù)據(jù)庫連接
     * @param  $argv [description] 配置參數(shù)
     */
    public static function init($connkey, $argv)
    {
        if (empty(self::$config[$connkey]['is_init'])) {
            Log::debug(__METHOD__ . " init start ");
            self::$config[$connkey]['max'] = $argv['max'];
            self::$config[$connkey]['min'] = $argv['min'];
            self::$config[$connkey]['is_init'] = true;
            self::$working_pool[$connkey] = array();
            self::$free_queue[$connkey] = new \SplQueue();
            self::$close_queue[$connkey] = new \SplQueue();
        }

    }

    /**
     * 開啟定時任務(wù)
     * @param $connkey
     * @param $argv
     */
    public static function start($connkey, $argv)
    {

        if (!self::$timer_start) {
            Log::debug(__METHOD__ . " schedule ");
            //定時更新過期資源
            //self::schedule($connkey, $argv);
            //定時回收數(shù)據(jù)庫連接資源
            self::recovery($connkey, $argv);
            self::$timer_start = true;
        }
    }

    /**
     * 獲取連接資源
     * @param $connkey
     * @param $argv
     * @return array
     */
    public static function getResource($connkey, $argv)
    {
        if(empty($argv['max'])) $argv['max'] = self::MAX_CONN;
        if(empty($argv['min'])) $argv['min'] = self::MIN_CONN;
        if(empty($argv['timeout'])) $argv['timeout'] = self::TIME_OUT;

        self::init($connkey, $argv);
        self::start($connkey, $argv);

        if (!self::$free_queue[$connkey]->isEmpty()) {
            //現(xiàn)有資源可處于空閑狀態(tài)
            $key = self::$free_queue[$connkey]->dequeue();
            Log::debug(__METHOD__ . " free queue  key == $key ", __CLASS__);

            return array(
                'r' => 0,
                'key' => $key,
                'data' => self::update($connkey, $key, $argv),
            );
        } elseif (count(self::$working_pool[$connkey]) < self::$config[$connkey]['max']) {
            Log::debug(__METHOD__ . " below max, current count:" . count(self::$working_pool[$connkey]), __CLASS__);
            if(self::$close_queue[$connkey]->isEmpty()) {
                $key = count(self::$working_pool[$connkey]);
            }
            else {
                $key = self::$close_queue[$connkey]->dequeue();
            }

            //當(dāng)前池可以再添加資源用于分配
            $resource = self::product($connkey, $argv);
            //product失敗
            if(!$resource) {
                Log::info('product resource error:' . $connkey . $key);
                return array('r' => 1);
            }

            self::$working_pool[$connkey][$key] = $resource;

            return array(
                'r' => 0,
                'key' => $key,
                'data' => self::$working_pool[$connkey][$key]['obj'],
            );
        } else {
            Log::error(__METHOD__ . " no resource can apply ", __CLASS__);
            return array('r' => 1);
        }

    }

    /**
     * [freeResource 釋放資源]
     * @param $connkey
     * @param $key
     */
    public static function freeResource($connkey, $key)
    {
        Log::debug(__METHOD__ . " key == $key", __CLASS__);
        self::$free_queue[$connkey]->enqueue($key);
        self::$working_pool[$connkey][$key]['status'] = 0;
    }

    /**
     * [schedule 定時調(diào)度 釋放過期資源]
     * @param $connkey
     * @param $argv
     */
    public static function schedule($connkey, $argv)
    {
        Log::debug(__METHOD__ . ' schedule start:' . $argv['timeout'] . 's');
        swoole_timer_tick($argv['timeout'] * 1000, function() use($argv) {
            Log::debug('schedule timer tick start');
            foreach (self::$working_pool as $connkey => $pool_data) {
                foreach ($pool_data as $key => $data) {
                    //當(dāng)前連接已超時
                    if($data['status'] != 0 && $data['lifetime'] < microtime(true)) {
                        //釋放資源
                        self::freeResource($connkey, $key);
                    }
                }
            }
        });
    }

    /**
     * 定時回收多余空閑連接資源
     * @param string $connkey
     * @param array $argv
     */
    public static function recovery($connkey, $argv)
    {
        Log::debug(__METHOD__ . ' recovery start:' . self::RECOVERY_TIME_INTERVAL);
        swoole_timer_tick(self::RECOVERY_TIME_INTERVAL, function() use($argv) {
            Log::debug('recovery timer tick start');
            foreach (self::$free_queue as $connkey => $queue) {
                if($queue->isEmpty() || $queue->count() <= $argv['min'])
                    continue;

                //空閑資源超過最小連接,關(guān)閉多余的數(shù)據(jù)庫連接
                for($i = $argv['min']; $i < $queue->count();) {
                    $key = $queue->dequeue();
                    //關(guān)閉數(shù)據(jù)庫連接
                    self::$working_pool[$connkey][$key]['obj']->close();
                    self::$close_queue[$connkey]->enqueue($key);
                    unset(self::$working_pool[$connkey][$key]);
                    Log::debug(__METHOD__ . ' key' . $key . ' queue count:' . $queue->count() . ' connect number:' . count(self::$working_pool[$connkey]));
                }
            }
        });
    }

    /**
     * [product 生產(chǎn)資源]
     * @param $connkey
     * @param $argv
     * @return mixed
     */
    private static function product($connkey, $argv)
    {
        //防止并發(fā)出現(xiàn)已超過連接數(shù)
        if(count(self::$working_pool[$connkey]) >= self::$config[$connkey]['max'])
            return false;

        $resource = $argv['db']->connect($argv['config']);
        if(!$resource) return false;
        return array(
            'obj' => $resource,  //實(shí)例
            'lifetime' => microtime(true) + ((float) $argv['timeout']),   //生命期
            'status' => 1, //狀態(tài) 1 在用 0 空閑
        );
    }

    /**
     * [update 更新資源]
     * @param string $connkey
     * @param string $key
     * @param array $argv
     * @return array
     */
    private static function update($connkey, $key, $argv)
    {
        self::$working_pool[$connkey][$key]['status'] = 1;
        self::$working_pool[$connkey][$key]['lifetime'] = microtime(true) + ((float) $argv['timeout']);
        return self::$working_pool[$connkey][$key]['obj'];
    }

    /**
     * 更新數(shù)據(jù)庫連接
     * @param string $connkey
     * @param string $key
     * @param array $argv
     * @return array
     */
    public static function updateConnect($connkey, $key, $argv)
    {
        //更新資源
        $argv['db']->close();
        $resource = $argv['db']->connect($argv['config']);
        self::$working_pool[$connkey][$key]['obj'] = $resource;
        self::$working_pool[$connkey][$key]['lifetime'] = microtime(true) + ((float) $argv['timeout']);
        Log::info('更新working pool key:' . $connkey . $key);
        return $resource;
    }
}

更完整的框架源碼請查閱我的github項(xiàng)目地址 https://github.com/jack15083/sample-swoole

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 更改ip和dnsVi /etc/sysconfig/network-scripts/ifcfg-eth0vi /...
    Xwei_閱讀 1,853評論 0 3
  • 1.LAMP介紹 ? LAM(M)P:L: linuxA: apache (httpd)M: mysql, mar...
    尛尛大尹閱讀 1,079評論 0 1
  • 當(dāng)我編寫一個類是,其實(shí)就是在描述對象的屬性和行為,而并沒有產(chǎn)生實(shí)質(zhì)上的對象,只有通過new關(guān)鍵字才會產(chǎn)生出對象,這...
    大晴天小陽光閱讀 247評論 0 0
  • 精神病級別級別的夢。比利維坦口味更重……身體深層的恐懼 不斷的去約會的夢。乳頭在感受男人和嬰兒之間的轉(zhuǎn)化。從女人到...
    白鯤夏貓閱讀 354評論 0 0
  • 許坤大學(xué)畢業(yè)兩年了,工作一直不順心,做了幾份工作,都是堅(jiān)持不了多久,他一度開始懷疑自己的學(xué)歷,懷疑自己的能力。在他...
    安靜寫字的女子閱讀 670評論 59 90