Swoole数据库连接池分析及实现

使用PHP swoole 由于其内存常驻及协程特性,一般是需要使用数据库链接池来减少链接创建的开支的,一个连接池的实现难点在哪,下面分析

1:如何判断是否该获取新的链接?
A:默认规则一个协程对应一个数据库连接,同一个协程里应该返回同一个链接,即保存链接时应该以协程id为key;

2:如何判断连接是否被使用?
在获取连接时我们是不能把正在使用的连接给丢给别的协程的,不然就出现数据混淆的风险,在获取数据库连接对象时应该保存该连接被使用的标记,当协程结束时 (Coroutine::defer 注册回调函数) 应该将其标记为空闲状态

3:什么时候回收?
数据库连接使用完,空闲连接过多应该是自动会回收的,连接池连接回收应该有定时任务检测,回收超出最小链接数的空闲链接

4:获取数据库连接时已经超出连接池最大链接数怎么处理?
正常情况这里应该是阻塞等待的状态,(swoole channel 实现)直到有链接被回收或者超时返回false

下面是用swoole 实现的伪代码,不是完整代码,可以看备注 查看主要实现

/**
* swoole 的数据库连接池实现 伪代码
*/
class DbPool {

    private $maxNum;
    private $minNum;
    private $unUseChannel=null; //保存空闲可用的数据库链接 Channel,由 swoole Channel实现,可阻塞等待
    private $useChannel=null; //保存已经在使用的数据库链接
    private static  $install=null;

    private function __construct($config)
    {
        $this->init($config);
    }

    //初始化
     private function init($config){

         //todo 处理配置,生成
         $this->maxNum=$config['maxNum'];
         $this->minNum=$config['minNum'];
         $this->unUseChannel = new Channel($this->maxNum + 8);
         $this->useChannel = [];
         $this->initConent($this->minNum);
         //开启定时任务检查
         $this->checkItems();

     }

    //初始化数据库链接
    private function initConent($min){
        if (!empty($this->unUseChannel)){
            for ($i=0;$i<$min;$i++){
                $connect=new Connect();
                $this->unUseChannel->push($connect);
         }
        }
    }


     //初始化注册
     public static function register($config){
         if (!empty(self::$install)){
             //已注册
             return false;
         }
       return  self::$install=new static($config);
     }

     //返回连接池实例
     public static function getInstant(){
         if (empty(self::$install)){
             //未注册
           return false;

         }
         return self::$install;
    }

    //定时任务实现检查可用链接 swoole timer实现定时任务
    private function checkItems(){
        //每5s检测
        Timer::tick(5*1000,function (){
            //超出个数
            $dels=$this->unUseChannel->length()-$this->minNum;
            if ($dels>0){
                for ($i=0;$i<$dels;$i++){
                    $connect=$this->unUseChannel->pop(0.01);
                    //关闭连接
                    $connect->close();
                }
            }

        });
    }

    //返回一个可用链接
    public function getConnect(){
        //获得当前协程id
        $cid=Coroutine::getCid();
        //判断当前协程是否已经有链接,且链接可用
        if (!empty($this->useChannel[$cid])&&$this->useChannel[$cid]->status==1){
       		 return $this->useChannel[$cid];
        }

        //判断是否要新建链接
        if (count($this->useChannel)<$this->maxNum&&$this->unUseChannel->length()==0){
             $connect=new Connect();
            $this->useChannel[$cid]=$connect;
            return $connect;
        }


        //从空闲队列中获取一个
        //Channel->pop(float $timeout = 0) : mixed;
        //返回值可以是任意类型的PHP变量,包括匿名函数和资源
        //通道并关闭时,执行失败返回false
        //$timeout指定超时时间,浮点型,单位为秒,最小粒度为毫秒,在规定时间内没有生产者push数据,将返回false
        $connect=$this->unUseChannel->pop(0.5);

        //注册协程关闭回调
        if (!empty($connect)){
            $this->useChannel[$cid]=$connect;
            $this->defer($cid);
            return $connect;
        }else{
            throw  new Exception("get connect time out");
        }

    }


    //回收链接,在协程关闭时调用
    private function recycleConnect($cid){
        $connect=$this->useChannel[$cid];
        if (!empty($connect)&&$connect->status==1){
            unset($this->useChannel[$cid]);
            $this->useChannel->push($connect);
            return true;
        }

        return false;
    }

    //注册协程关闭回调函数,swoole Coroutine实现
    public function defer($cid){
        Coroutine::defer(function () use ($cid) {
            $this->recycleConnect($cid);
        });
    }


}

你可能感兴趣的:(mysql,PHP,php,mysql连接池,swoole,连接池,数据库连接池,php连接池,连接池实现)