swoolechannel之mysql连接池实现

上一篇文章介绍了利用channel来实现waitgroup和进程间通信的功能,本篇文章将继续利用channel来实现一个简单的mysql连接池,并且介绍利用新的的特性:defer来实现资源的回收为什么要实现mysql连接池?有以下几个原因:

保持长连接可以节省连接相关的开销(不过由于swoole本身常驻进程,所以只要不手工close,也还是长连接了)

mysql本身对连接有限制,所以每个请求(协程)都建立一个连接很容易导致mysql连接被打满

下面就来看一段代码,看swoole里实现一个连接池是怎么样的简单

<?phpuse Swoole\Coroutine\MySQL;class MysqlPool{private static $instance;private $pool;//连接池容器,一个channel    private $config;/**     * @param null $config     * @return MysqlPool     * @desc 获取连接池实例     */    public static function getInstance($config = null){if (empty(self::$instance)) {if (empty($config)) {throw new RuntimeException("mysql config empty");}self::$instance = new static($config);}return self::$instance;}/**     * MysqlPool constructor.     * @param $config     * @desc 初始化,自动创建实例,需要放在workerstart中执行     */    public function __construct($config){if (empty($this->pool)) {$this->config = $config;$this->pool = new chan($config[pool_size]);for ($i = 0; $i < $config[pool_size]; $i++) {$mysql = new MySQL();$res = $mysql->connect($config);if ($res == false) {//连接失败,抛弃常                    throw new RuntimeException("failed to connect mysql server.");} else {//mysql连接存入channel                    $this->put($mysql);}}}}/**     * @param $mysql     * @desc 放入一个mysql连接入池     */    public function put($mysql){$this->pool->push($mysql);}/**     * @return mixed     * @desc 获取一个连接,当超时,返回一个异常     */    public function get(){$mysql = $this->pool->pop($this->config[pool_get_timeout]);if (false === $mysql) {throw new RuntimeException("get mysql timeout, all mysql connection is used");}return $mysql;}/**     * @return mixed     * @desc 获取当时连接池可用对象     */    public function getLength(){return $this->pool->length();}}

那如何使用呢?继续看一段代码:

<?phprequire_once "mysql_pool.php";$config = [host => 127.0.0.1, //数据库ip    port => 3306,//数据库端口    user => root,//数据库用户名    password => , //数据库密码    database => test, //默认数据库名    timeout => 0.5, //数据库连接超时时间    charset => utf8mb4, //默认字符集    strict_type => true,//ture,会自动表数字转为int类型    pool_size => 3, //连接池大小    pool_get_timeout => 0.5, //当在此时间内未获得到一个连接,会立即返回。(表示所以的连接都已在使用中)];//创建http server$http = new Swoole\Http\Server("0.0.0.0", 9501);$http->set([//"daemonize" => true,    "worker_num" => 1,"log_level" => SWOOLE_LOG_ERROR,]);$http->on(WorkerStart, function ($serv, $worker_id) use ($config) {//worker启动时,每个进程都初始化连接池,在onRequest中可以直接使用    try {MysqlPool::getInstance($config);} catch (\Exception $e) {//初始化异常,关闭服务        echo $e->getMessage() . PHP_EOL;$serv->shutdown();} catch (\Throwable $throwable) {//初始化异常,关闭服务        echo $throwable->getMessage() . PHP_EOL;$serv->shutdown();}});$http->on(request, function ($request, $response) {//浏览器会自动发起这个请求,这也是很多人碰到的一个问题:    //为什么我浏览器打开网站,收到了两个请求?    if ($request->server[path_info] == /favicon.ico) {$response->end();return;}//获取数据库    if ($request->server[path_info] == /list) {go(function () use ($request, $response) {//从池子中获取一个实例            try {$pool = MysqlPool::getInstance();$mysql = $pool->get();defer(function () use ($mysql) {//利用defer特性,可以达到协程执行完成,归还$mysql到连接池                    //好处是 可能因为业务代码很长,导致乱用或者忘记把资源归还                    MysqlPool::getInstance()->put($mysql);echo "当前可用连接数:" . MysqlPool::getInstance()->getLength() . PHP_EOL;});$result = $mysql->query("select * from test");$response->end(json_encode($result));} catch (\Exception $e) {$response->end($e->getMessage());}});return;}//模拟timeout, 浏览器打开4个tab,都请求 :9501/timeout,前三个应该是等10秒出结果,第四个500ms后出超时结果    //ps: chrome浏览器,需要加一个随机数,:9501/timeout?t=0, :9501/timeout?t=1, 因为chrome会对完全一样的url做并发请求限制    echo "get request:".time().PHP_EOL;if ($request->server[path_info] == /timeout) {go(function () use ($request, $response) {//从池子中获取一个实例            try {$pool = MysqlPool::getInstance();echo "当前可用连接数:" . $pool->getLength() . PHP_EOL;$mysql = $pool->get();echo "当前可用连接数:" . $pool->getLength() . PHP_EOL;defer(function () use ($mysql) {//协程执行完成,归还$mysql到连接池                    MysqlPool::getInstance()->put($mysql);echo "当前可用连接数:" . MysqlPool::getInstance()->getLength() . PHP_EOL;});$result = $mysql->query("select * from test");\Swoole\Coroutine::sleep(10); //sleep 10秒,模拟耗时操作                $response->end(json_encode($result));} catch (\Exception $e) {$response->end($e->getMessage());}});return;}});$http->start();

几个重点

在workerStart初始化连接,可以做一些前置判断

利用defer特性,以免乱用或忘记归还资源,减轻开发心智负担

修改自己的数据配置,和执行语句,然后浏览器执行: :9501/list  可以看到正常的结果输出:9501/timeout 演示连接池取和存的过程,大家也可以实验一下(注意看注释里的说明)

为什么用channel? 

不用channel,直接用一个array或者sqlQueue也是可以的,用channel有几个好处

channel也是可被协程调度的

channel->pop的时候,可以设置超时,用array 或者 sqlQueue 实现就会麻烦一些

需要注意的点

由于swoole是多进程架构,直连mysql的话,连接数=worker_num * pool_size

defer需要swoole版本 >= 4.2.9

预告:本篇只是实现一个简单的mysql连接池,下篇将完善这个连接池,能够做到异常捕获和处理,以及如何处理非常经典『MySQL server has gone away』查看原文,可以了解swoole/mysql相关的姿势

--------------伟大的分割线----------------

PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!

饭米粒只发原创或授权发表的文章,不转载网上的文章

所发的文章,均可找到原作者进行沟通。

也希望各位多多打赏(算作稿费给文章作者),更希望大家多多投搞。

投稿请联系:

[email protected]

本文由 半桶水 授权 饭米粒 发布,转载请注明本来源信息和以下的二维码(长按可识别二维码关注)