swoole channel之mysql连接池实现(Php使用swoole)

网友投稿 959 2022-08-28

swoole channel之mysql连接池实现(Php使用swoole)

swoole channel之mysql连接池实现(Php使用swoole)

本篇文章将利用channel来实现一个简单的mysql连接池,并且介绍利用新的的特性:defer来实现资源的回收

为什么要实现mysql连接池?

有以下几个原因:

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

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

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

{ 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();

}

}

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

$config = [ 'host' => '127.0.0.1', //数据库ip    'port' => 3306, //数据库端口    'user' => 'root', //数据库用户名    'password' => '123456', //数据库密码    '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,都请求 http://127.0.0.1:9501/timeout,前三个应该是等10秒出结果,第四个500ms后出超时结果    //ps: chrome浏览器,需要加一个随机数,http://127.0.0.1:9501/timeout?t=0, http://127.0.0.1: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特性,以免乱用或忘记归还资源,减轻开发心智负担

修改自己的数据配置,和执行语句,然后浏览器执行: 

http://127.0.0.1:9501/list  可以看到正常的结果输出

http://127.0.0.1:9501/timeout 演示连接池取和存的过程,大家也可以实验一下(注意看注释里的说明)

为什么用channel?

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

channel也是可被协程调度的

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

需要注意的点

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

defer需要swoole版本 >= 4.2.9

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:数据结构与算法(一)
下一篇:说说 MyBatis 二级缓存?关联刷新实现?
相关文章

 发表评论

暂时没有评论,来抢沙发吧~