协程连接池
连接池这个东西即使没用过,你也应该听说过,特别是做过 Java 等其它语言开发的同学,对这玩意绝对不会陌生。今天,我们就来讲讲 Swoole 中如何应用连接池。
连接池
连接池,概念不细讲了。反正你要知道,对于一次查询来说,建立连接是非常耗时的。而连接池,则是将连接保存起来,需要用的时候直接取出来一个,不用每次都创建新的连接,从而极大地提升数据查询的效率。
Swoole 中的连接池,是基于协程的,并且也是通过 Channel 自动调度的,你不用管太多别的,只管用就是了。它为我们默认准备好了三种连接池,分别是 PDOPool、MysqliPool、RedisPool 。相信不用我多解释了,就是 MySQL 数据库和 Redis 的连接池应用。它们都支持自动断线重连,可以恢复大部分连接上下文,处于事务中的连接如果断开,是无法恢复上下文的,而且会抛出异常。如果有连接对象出现异常不可用的情况,需要调用一个 put(null) 方法,归还一个空连接以保证连接池的数量平衡。
每个连接池,都提供了四个方法。
- get 方法获取连接(连接池未满时会创建新的连接)
- put 方法回收连接
- fill 方法填充连接池(提前创建连接)
- close 关闭连接池
理论方面的东西就是这些,我们直接来看看怎么用。Mysqli 现在使用的已经越来越少了,所以我们就只看看 PDO 和 Redis 的使用。
PDO 连接池
代码语言:javascript复制$time = microtime(true);
SwooleCoroutinerun(function(){
$pdoConfig = new SwooleDatabasePDOConfig();
$pdoConfig
->withUnixSocket('/var/lib/mysql/mysql.sock')
// ->withHost('127.0.0.1')
// ->withPort(3306)
->withDbName('test')
->withCharset('utf8mb4')
->withUsername('root')
->withPassword('123456');
$pool = new SwooleDatabasePDOPool($pdoConfig, 2);
for($i = 4;$i--;){
go(function()use($pool){
$pdo = $pool->get();
$statement = $pdo->prepare('SELECT ? ?');
if (!$statement) {
throw new RuntimeException('Prepare failed');
}
$a = mt_rand(1, 100);
$b = mt_rand(1, 100);
$result = $statement->execute([$a, $b]);
if (!$result) {
throw new RuntimeException('Execute failed');
}
$result = $statement->fetchAll();
if ($a $b !== (int)$result[0][0]) {
throw new RuntimeException('Bad result');
}
echo spl_object_id($pdo), PHP_EOL; // 打印 pdo 对象 id
$pool->put($pdo);
});
}
});
echo microtime(true) - $time, PHP_EOL;
//11
//8
//8
//11
//0.0019669532775879
PDOPool 是 PDO 的连接池对象,它需要两个构造参数:第一个参数是一个 PDOConfig 对象,可以看到这个对象主要就是我们连接的参数信息;第二个参数是连接池的数量,默认是 64 ,为了方便测试,目前我们先设置成 2 。
连接池对象准备好之后,创建 4 个协程,在这些协程中使用连接池去请求 MySQL 查询,当然,并没有查询什么真的表,只是做一个简单的计算操作,如果执行或计算失败,会抛出异常。最后,我们还打印了连接对象的 ID 。
从输出的内容可以看出,连接对象只有两个,它们会来回重复使用。这就是连接池的作用,我们不必重复地创建连接对象,节省建立连接的时间。由于我们的数量比较小,看不出什么效果,大家可以加大协程数量,比如我们将 $i
改为 1024 。
// 1024 2 0.23715400695801
// 1024 10 0.13657021522522
1024 个协程,2 个连接的连接池执行的结果是 0.237 秒。10 个连接的连接池的执行时间是 0.136 秒。这个提升还是比较明显吧,不过我们的操作太简单了,如果有更复杂的查询,或者真实的业务场景,提升的效果还会更明显。
Redis 连接池
Redis 连接池的设置配置和 PDO 区别不大,只是 RedisConfig 的方法参数不同而已。
代码语言:javascript复制$time = microtime(true);
SwooleCoroutinerun(function(){
$pool = new SwooleDatabaseRedisPool((new SwooleDatabaseRedisConfig())
->withHost('127.0.0.1')
->withPort(6379)
->withAuth('')
->withDbIndex(0)
->withTimeout(1)
, 2);
for ($n = 4; $n--;) {
go(function () use ($pool) {
$redis = $pool->get();
$result = $redis->set('foo', 'bar');
if (!$result) {
throw new RuntimeException('Set failed');
}
$result = $redis->get('foo');
if ($result !== 'bar') {
throw new RuntimeException('Get failed');
}
echo spl_object_id($redis), PHP_EOL; // 打印 pdo 对象 id
$pool->put($redis);
});
}
});
echo microtime(true) - $time, PHP_EOL;
//14
//16
//14
//16
//0.0018310546875
我们同样可以看出是两个相同的连接对象 ID 在切换。然后你也可以自己再调大创建的协程数量以及调整连接池数据进行测试。
连接池设置多大
连接池的数量可不是随便设置的,第一点,你不能超过对方系统所支持的连接数量。比如说你设置个 1000 ,但 MySQL 只配置 max_connections 支持 100 个连接,那么直接就会报错,这个大家可以试试。
PostgreSQL 提供的一个公式是 连接数 = ((核心数 * 2) 有效磁盘数) ,比如你是 4核 的服务器,那么连接池数量设置为 ((4*2) 1)=9 个就可以了。
具体的内容其实还是和底层的进程、线程、协程相关的知识有关,我也没法讲得太深入,但是,一般情况下,确实不用设置太多。在 PDOPool 和 RedisPool 的构造函数中,连接数这个值是可以不用设置的,默认它会给一个 64 ,如果没有别的特殊情况,直接使用这个默认值也是没什么问题的。
总结
今天的内容也很简单吧?连接池在别的开发语言中也应用得非常广泛,但在 PHP 中确实还是比较少见的,还是那句话,转换思维,接纳更多的知识。就像你学过 Java 的话,对这个东西的理解就是完全没难度的,反过来,现在你深入的再去自己查资料更详细的了解一下连接池,那么将来看 Java 相关的项目时,也不会再因为这个问题而产生困扰了。程序语言的学习就是这样,一个通了,其它语言的实现也只是大差不差的。
测试代码:
https://github.com/zhangyue0503/swoole/blob/main/4.Swoole协程/source/4.6协程连接池.php
参考文档:
https://wiki.swoole.com/#/coroutine/conn_pool