* [Pool](#Pool) * [安装](#安装) * [基础实现代码](#基础实现代码) * [定义池对象](#定义池对象) * [定义池](#定义池) * [使用](#使用) * [连接池实现示例](#连接池实现示例) * [安装easyswoole/redis组件](#安装easyswoole/redis组件) * [新增RedisPool管理器](#新增RedisPool管理器) * [连接池配置项](#连接池配置项) * [池管理器](#池管理器) * [池对象方法](#池对象方法) # Pool 通用的连接池管理。 ## 安装 ```bash composer require easyswoole/pool ``` ## 基础示例代码 ### 定义池对象 ```php 不一定非要在创建对象方法 `createObject()` 中返回 ```EasySwoole\Pool\ObjectInterface``` 对象,任意类型对象均可 ### 使用 ```php getObj(); $obj2 = $pool->getObj(); var_dump($obj->who()); var_dump($obj2->who()); }); ``` ## Redis连接池实现示例 ### 安装 easyswoole/redis 组件: ```bash composer require easyswoole/redis ``` ### 新增RedisPool管理器 新增文件 `\App\Pool\RedisPool.php`,内容如下: RedisPool 管理器(基于 `AbstractPool` 实现): ```php redisConfig = $redisConfig; } protected function createObject() { // 根据传入的 redis 配置进行 new 一个 redis 连接对象 $redis = new Redis($this->redisConfig); return $redis; } } ``` 或使用 RedisPool 管理器(基于 `MagicPool` 实现): ```php getConf('REDIS1')); $redisConfig2 = new \EasySwoole\Redis\Config\RedisConfig(\EasySwoole\EasySwoole\Config::getInstance()->getConf('REDIS2')); // 注册连接池管理对象 \EasySwoole\Pool\Manager::getInstance()->register(new \App\Pool\RedisPool($config, $redisConfig1), 'redis1'); \EasySwoole\Pool\Manager::getInstance()->register(new \App\Pool\RedisPool($config, $redisConfig2), 'redis2'); ``` 调用(可在控制器中全局进行调用): ```php get('redis1')->getObj(); $redis2 = \EasySwoole\Pool\Manager::getInstance()->get('redis2')->getObj(); $redis1->set('name', '仙士可'); var_dump($redis1->get('name')); $redis2->set('name', '仙士可2号'); var_dump($redis2->get('name')); // 回收连接对象(将连接对象重新归还到连接池,方便后续使用) \EasySwoole\Pool\Manager::getInstance()->get('redis1')->recycleObj($redis1); \EasySwoole\Pool\Manager::getInstance()->get('redis2')->recycleObj($redis2); // 释放连接对象(将连接对象直接彻底释放,后续不再使用) // \EasySwoole\Pool\Manager::getInstance()->get('redis1')->unsetObj($redis1); // \EasySwoole\Pool\Manager::getInstance()->get('redis2')->unsetObj($redis2); }); ``` ## 连接池配置项 在实例化一个连接池对象时,需要传入一个连接池配置对象 `EasySwoole\Pool\Config`,该对象的属性如下: | 配置项 | 默认值 | 说明 | 备注 | |:-------------------|:--------|:------------------------|:--------------------------------------------------------------------------------------| | $intervalCheckTime | 15 * 1000 | 定时器执行频率(毫秒),默认值为 `15` s | 用于定时执行连接池对象回收,创建操作 | | $maxIdleTime | 10 | 连接池对象最大闲置时间(秒) | 超过这个时间未使用的对象将会被定时器回收 | | $maxObjectNum | 20 | 连接池最大数量 | 每个进程最多会创建 `$maxObjectNum` 连接池对象,如果对象都在使用,则会返回空,或者等待连接空闲 | | $minObjectNum | 5 | 连接池最小数量(热启动) | 当连接池对象总数低于 `$minObjectNum` 时,会自动创建连接,保持连接的活跃性,让控制器能够尽快地获取连接 | | $getObjectTimeout | 3.0 | 获取连接池中连接对象的超时时间 | 当连接池为空时,会等待`$getObjectTimeout` 秒,如果期间有连接空闲,则会返回连接对象,否则返回 `null` | | $extraConf | | 额外配置信息 | 在实例化连接池前,可以把一些额外配置放到这里,例如数据库配置信息、`redis` 配置等等 | | $loadAverageTime | 0.001 | 负载阈值 | 并发来临时,连接池内对象达到`maxObjectNum`,此时并未达到 `intervalCheckTime` 周期检测,因此设定了一个 `5s` 负载检测,当 `5s` 内,取出总时间/取出连接总次数,会得到一个平均取出时间,如果小于此阈值,说明此次并发峰值非持续性,将回收 `5%` 的连接 | ## 池管理器 池管理器可以做全局的连接池管理,例如在 `EasySwooleEvent.php` 中的 `initialize` 事件中注册,然后可以在控制器中获取连接池然后进行获取连接: ```php getConf('REDIS1')); $redisConfig2 = new \EasySwoole\Redis\Config\RedisConfig(Config::getInstance()->getConf('REDIS2')); // 注册连接池管理对象 \EasySwoole\Pool\Manager::getInstance()->register(new \App\Pool\RedisPool($config,$redisConfig1), 'redis1'); \EasySwoole\Pool\Manager::getInstance()->register(new \App\Pool\RedisPool($config,$redisConfig2), 'redis2'); } ``` 在控制器中获取连接池连接进行调用: ```php get('redis1')->getObj(); $redis2 = \EasySwoole\Pool\Manager::getInstance()->get('redis1')->getObj(); $redis1->set('name','仙士可'); var_dump($redis1->get('name')); $redis2->set('name','仙士可2号'); var_dump($redis2->get('name')); // 回收连接对象(将连接对象重新归还到连接池,方便后续使用) \EasySwoole\Pool\Manager::getInstance()->get('redis1')->recycleObj($redis1); \EasySwoole\Pool\Manager::getInstance()->get('redis2')->recycleObj($redis2); // 释放连接对象(将连接对象直接彻底释放,后续不再使用) // \EasySwoole\Pool\Manager::getInstance()->get('redis1')->unsetObj($redis1); // \EasySwoole\Pool\Manager::getInstance()->get('redis2')->unsetObj($redis2); } ``` ## 池对象方法 | 方法名称 | 参数 | 说明 | 备注 | |:--------------|:-----------------------------------------|:-----------------------------------------------------------|:--------------------------------------------| | createObject | | 抽象方法,创建连接对象 | | | recycleObj | $obj | 回收一个连接 | | | getObj | float $timeout = null, int $tryTimes = 3 | 在指定的超时时间 `$timeout` (秒)内获取一个连接,会重复尝试获取 `$tryTimes` 次直到获取到,获取失败则返回 `null` | | | unsetObj | $obj | 直接释放一个连接 | | | idleCheck | int $idleTime | 回收超过 `$idleTime` 未出队使用的连接 | | | intervalCheck | | 回收连接,以及热启动方法,允许外部调用热启动 | | | keepMin | ?int $num = null | 保持最小连接(热启动) | | | getConfig | | 获取连接池的配置信息 | | | status | | 获取连接池状态信息 | 获取当前连接池已创建、已使用、最大创建、最小创建数据 | | isPoolObject | $obj | 查看 `$obj` 对象是否由该连接池创建 | | | isInPool | $obj | 获取当前连接是否在连接池内未使用 | | | destroy | | 销毁该连接池 | | | reset | | 重置该连接池 | | | invoke | callable $call,float $timeout = null | 获取一个连接,传入到 `$call` 回调函数中进行处理,回调结束后自动回收连接 | | | defer | float $timeout = null | 获取一个连接,协程结束后自动回收 | | ### getObj 获取一个连接池的连接对象: ```php getConf('REDIS'))); // 获取一个连接池的对象 $redis = $redisPool->getObj(); var_dump($redis->echo('仙士可')); $redisPool->recycleObj($redis); }); ``` ::: warning 通过 `getObj` 方法获取的对象,都必须调用 `recycleObj` 或者 `unsetObj` 方法进行回收,否则连接池对象会越来越少。 ::: ### unsetObj 直接释放一个连接池的连接对象,其他协程不能再获取到这个连接对象,而是会重新创建一个连接对象 ::: warning 释放之后,并不会立即销毁该对象,而是会在作用域结束之后销毁 ::: ### recycleObj 回收一个连接对象,回收之后,其他协程可以正常获取这个连接对象。 ::: warning 回收之后,其他协程可以正常获取这个连接,但在此时,该连接还处于当前协程中,如果再次调用该连接进行数据操作,将会造成协程混乱,所以需要开发人员自行约束,当对这个连接对象进行 `recycleObj` 操作后不能再操作这个对象 ::: ### invoke 获取一个连接,传入到 `$call` 回调函数中进行处理,回调结束后自动回收连接: ```php getConf('REDIS'))); $redisPool->invoke(function (\EasySwoole\Redis\Redis $redis) { var_dump($redis->echo('仙士可')); }); }); ``` ::: warning 通过该方法无需手动回收连接,在回调函数结束后,则自动回收 ::: ### defer 获取一个连接,协程结束后自动回收 ```php getConf('REDIS'))); $redis = $redisPool->defer(); var_dump($redis->echo('仙士可')); }); ``` ::: warning 通过该方法无需手动回收连接,在协程结束后,则自动回收 ::: ::: warning 需要注意的事,`defer` 方法是协程结束后才回收,如果你当前协程运行时间过长,则会一直无法回收,直到协程结束 ::: ### keepMin 保持最小连接(热启动)。 由于 `easyswoole/pool` 当刚启动服务,出现过大的并发时,可能会突然需要几十个甚至上百个连接,这时为了让创建连接的时间分散,可以通过调用 `keepMin` 方法进行预热启动连接。 调用此方法后,将会预先创建 `N` 个连接,用于服务启动之后的控制器直接获取连接: 预热使用示例如下: 在 `EasySwooleEvent.php` 中的 `mainServerCreate` 中,当 `Worker` 进程启动后,热启动连接: ```php add($register::onWorkerStart,function (\swoole_server $server,int $workerId){ if ($server->taskworker == false) { //每个worker进程都预创建连接 \EasySwoole\Pool\Manager::getInstance()->get('redis')->keepMin(10); var_dump(\EasySwoole\Pool\Manager::getInstance()->get('redis')->status()); } }); } ``` 将会输出: ``` array(4) { ["created"]=> int(10) ["inuse"]=> int(0) ["max"]=> int(20) ["min"]=> int(5) } ``` ::: warning `keepMin` 是根据不同进程,创建不同的连接的,比如你有 `10` 个 `Worker` 进程,将会输出 `10` 次,总共创建 `10 * 10 = 100` 个连接 ::: ### getConfig 获取连接池的配置: ```php getConf('REDIS'))); var_dump($redisPool->getConfig()); ``` ### destroy 销毁连接池。 调用之后,连接池剩余的所有链接都会被执行 `unsetObj`,并且将关闭连接队列,调用之后 `getObj` 等方法都将失效: ```php getConf('REDIS'))); var_dump($redisPool->getObj()); $redisPool->destroy(); var_dump($redisPool->getObj()); }); ``` ### reset 重置连接池。 调用 `reset` 之后,会自动调用 `destroy` 销毁连接池,并在下一次 `getObj` 时重新初始化该连接池: ```php getConf('REDIS'))); var_dump($redisPool->getObj()); $redisPool->reset(); var_dump($redisPool->getObj()); }); ``` ### status 获取连接池当前状态,调用之后将输出: ``` array(4) { ["created"]=> int(10) ["inuse"]=> int(0) ["max"]=> int(20) ["min"]=> int(5) } ``` ### idleCheck 回收空闲超时的连接 ### intervalCheck 调用此方法后,将调用 `idleCheck` 和 `keepMin` 方法,用于手动回收空闲连接和手动热启动连接 ```php idleCheck($this->getConfig()->getMaxIdleTime()); $this->keepMin($this->getConfig()->getMinObjectNum()); } ```