zoukankan      html  css  js  c++  java
  • 如何在 Swoole 中优雅的实现 MySQL 连接池

    如何在 Swoole 中优雅的实现 MySQL 连接池

    一、为什么需要连接池 ?

    数据库连接池指的是程序和数据库之间保持一定数量的连接不断开,
    并且各个请求的连接可以相互复用,
    减少重复连接数据库带来的资源消耗,
    一定程度上提高了程序的并发性能。

    二、连接池实现要点

    • 协程:使用 MySQL 协程客户端。

    使用 MySQL 协程客户端,是为了能在一个 Worker 阻塞的时候,
    让出 CPU 时间片去处理其他的请求,提高整个 Worker 的并发能力。

    • 连接池存储介质:使用 swoolecoroutinechannel 通道。

    使用 channel 能够设置等待时间,等待其他的请求释放连接。
    并且在等待期间,同样也可以让出 CPU 时间片去处理其他的请求。

    假设选择 array 或 splqueue,无法等待其他的请求释放连接。
    那么在高并发下的场景下,可能会出现连接池为空的现象。
    如果连接池为空了,那么 pop 就直接返回 null 了,导致连接不可用。

    注:因此不建议选择 array 或 splqueue。

    三、连接池的具体实现

    <?php
    
    use SwooleCoroutineChannel;
    use SwooleCoroutineMySQL;
    
    class MysqlPool
    {
        private $min; // 最小连接数
        private $max; // 最大连接数
        private $count; // 当前连接数
        private $connections; // 连接池
        protected $freeTime; // 用于空闲连接回收判断
    
        public static $instance;
    
        /**
         * MysqlPool constructor.
         */
        public function __construct()
        {
            $this->min = 10;
            $this->max = 100;
            $this->freeTime = 10 * 3600;
            $this->connections = new Channel($this->max + 1);
        }
    
        /**
         * @return MysqlPool
         */
        public static function getInstance()
        {
            if (is_null(self::$instance)) {
                self::$instance = new self();
            }
    
            return self::$instance;
        }
    
        /**
         * 创建连接
         * @return MySQL
         */
        protected function createConnection()
        {
            $conn = new MySQL();
            $conn->connect([
                'host' => 'mysql',
                'port' => '3306',
                'user' => 'root',
                'password' => 'root',
                'database' => 'fastadmin',
                'timeout'  => 5
            ]);
    
            return $conn;
        }
    
        /**
         * 创建连接对象
         * @return array|null
         */
        protected function createConnObject()
        {
            $conn = $this->createConnection();
            return $conn ? ['last_used_time' => time(), 'conn' => $conn] : null;
        }
    
        /**
         * 初始化连接
         * @return $this
         */
        public function init()
        {
            for ($i = 0; $i < $this->min; $i++) {
                $obj = $this->createConnObject();
                $this->count++;
                $this->connections->push($obj);
            }
    
            return $this;
        }
    
        /**
         * 获取连接
         * @param int $timeout
         * @return mixed
         */
        public function getConn($timeout = 3)
        {
            if ($this->connections->isEmpty()) {
                if ($this->count < $this->max) {
                    $this->count++;
                    $obj = $this->createConnObject();
                } else {
                    $obj = $this->connections->pop($timeout);
                }
            } else {
                $obj = $this->connections->pop($timeout);
            }
    
            return $obj['conn']->connected ? $obj['conn'] : $this->getConn();
        }
    
        /**
         * 回收连接
         * @param $conn
         */
        public function recycle($conn)
        {
            if ($conn->connected) {
                $this->connections->push(['last_used_time' => time(), 'conn' => $conn]);
            }
        }
    
        /**
         * 回收空闲连接
         */
        public function recycleFreeConnection()
        {
            // 每 2 分钟检测一下空闲连接
            swoole_timer_tick(2 * 60 * 1000, function () {
               if ($this->connections->length() < intval($this->max * 0.5)) {
                   // 请求连接数还比较多,暂时不回收空闲连接
                   return;
               }
    
               while (true) {
                   if ($this->connections->isEmpty()) {
                       break;
                   }
    
                   $connObj = $this->connections->pop(0.001);
                   $nowTime = time();
                   $lastUsedTime = $connObj['last_used_time'];
    
                   // 当前连接数大于最小的连接数,并且回收掉空闲的连接
                   if ($this->count > $this->min && ($nowTime - $lastUsedTime > $this->freeTime)) {
                       $connObj['conn']->close();
                       $this->count--;
                   } else {
                       $this->connections->push($connObj);
                   }
               }
            });
        }
    }
    
    $httpServer = new swoole_http_server('127.0.0.1',9501);
    $httpServer->set(['work_num' => 1]);
    $httpServer->on('WorkerStart', function ($request, $response) {
        MysqlPool::getInstance()->init()->recycleFreeConnection();
    });
    $httpServer->on('Request', function ($request, $response){
        $conn = MysqlPool::getInstance()->getConn();
        $conn->query('SELECT * FROM fa_admin WHERE id=1');
        MysqlPool::getInstance()->recycle($conn);
    });
    $httpServer->start();
    

    四、总结

    • 定时维护空闲连接到最小值。
    • 使用用完数据库连接之后,需要手动回收连接到连接池。
    • 使用 channel 作为连接池的存储介质。
  • 相关阅读:
    Transfer: Javascript实现网页水印(非图片水印)
    VirtualPathUtility class from MSDN
    深入理解Flink 系统内部消息传递的exactly once语义
    深入理解Flink EndtoEnd ExactlyOnce语义
    深入理解Flink Metrics的内部结构
    深度优化sql 查询, 提升性能一百倍是什么概念?
    asp.net mvc+asp.net webform: a way of RIA + RAD
    使用scheduled task, 让电脑音乐伴你入眠
    A small breaking change in IE8 causing big pain in the ass: the default type of button element
    tsql 和 clr 的性能实测比对
  • 原文地址:https://www.cnblogs.com/yxhblogs/p/11055280.html
Copyright © 2011-2022 走看看