利用 Swoole 优雅的实现 MySQL 连接池

利用 Swoole 优雅的实现 MySQL 连接池
 

一、为什么需要连接池 ?

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

二、连接池实现要点

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

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

  • 连接池存储介质:使用 \swoole\coroutine\channel 通道。

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

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

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

三、连接池的具体实现

<?php
use Swoole\Coroutine\Channel;
use Swoole\Coroutine\MySQL;
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 作为连接池的存储介质
本博客所有文章如无特别注明均为原创。作者:长颈鹿复制或转载请以超链接形式注明转自 SZY
原文地址《利用 Swoole 优雅的实现 MySQL 连接池

相关推荐

发表评论

路人甲 表情
Ctrl+Enter快速提交

网友评论(0)