连接池组件

EasySwoole在基础组件中集合了连接池组件,增加mysql/redis等i/o连接的复用性,命名空间为:EasySwoole\Component\Pool
demo地址:(https://github.com/easy-swoole/demo/tree/3.x-pool)

在新版本中,实现了连接池自动回收,自动注册,匿名连接池注册,以及本身的底层异常处理使得用户在使用连接池时,可做到直接使用,无需注册,无需回收,也不会出现问题

PoolManager

连接池管理工具,提供了以下方法:

  1. <?php
  2. /**
  3. * 获取连接池默认配置
  4. * getDefaultConfig
  5. * @author Tioncico
  6. * Time: 20:34
  7. */
  8. function getDefaultConfig(){}
  9. /**
  10. * 注册连接池
  11. * register
  12. * @param string $className
  13. * @param int $maxNum 最大连接数量
  14. * @author Tioncico
  15. * Time: 20:34
  16. */
  17. function register(string $className, $maxNum = 20){}
  18. /**
  19. * 注册匿名连接池
  20. * registerAnonymous
  21. * @param string $name
  22. * @param callable|null $createCall
  23. * @author Tioncico
  24. * Time: 20:35
  25. */
  26. function registerAnonymous(string $name,?callable $createCall = null){}
  27. /**
  28. * 获取连接池的一个连接对象
  29. * getPool
  30. * @param $key
  31. * @author Tioncico
  32. * Time: 20:36
  33. */
  34. function getPool($key){}

AbstractPool

连接池实现接口,通过继承连接池实现接口,来创建一个连接池,例:

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Tioncico
  5. * Date: 2019/3/5 0005
  6. * Time: 20:42
  7. */
  8. namespace App\Utility\Pool;
  9. use EasySwoole\Component\Pool\AbstractPool;
  10. use EasySwoole\Mysqli\Config;
  11. use EasySwoole\Mysqli\Mysqli;
  12. class MysqlPool extends AbstractPool
  13. {
  14. protected function createObject()
  15. {
  16. //当连接池第一次获取连接时,会调用该方法
  17. //我们需要在该方法中创建连接
  18. //返回一个对象实例
  19. //必须要返回一个实现了AbstractPoolObject接口的对象
  20. $conf = \EasySwoole\EasySwoole\Config::getInstance()->getConf("MYSQL");
  21. $dbConf = new Config($conf);
  22. return new MysqlObject($dbConf);
  23. // TODO: Implement createObject() method.
  24. }
  25. }

方法如下:

  1. <?php
  2. abstract protected function createObject(){};//当获取连接池对象时并未达到最大连接数时会调用此方法创建连接池对象
  3. public function recycleObj($obj){};//释放一个连接池对象
  4. public function getObj(float $timeout = null, int $beforeUseTryTimes = 3){};//获取一个连接池对象
  5. public function unsetObj($obj){};//彻底释放一个连接池对象 /关闭连接
  6. public function gcObject(int $idleTime){};//清理超时连接
  7. protected function intervalCheck(){};//定时调用清理超时连接以及新创建连接逻辑
  8. protected function getPoolConfig(){};//获取当前连接池配置
  9. public function keepMin(?int $num = null){};//保持最小连接,当不够时创建
  10. public function preLoad(?int $num = null){};//热启动,keepMin的别名
  11. public function getConfig(){};//getPoolConfig别名
  12. public function status(){};//获取当前连接池状态

PoolObjectInterface

通过实现PoolObjectInterface接口,去创建一个连接池对象,例如

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Tioncico
  5. * Date: 2019/3/5 0005
  6. * Time: 20:42
  7. */
  8. namespace App\Utility\Pool;
  9. use EasySwoole\Component\Pool\PoolObjectInterface;
  10. use EasySwoole\Mysqli\Mysqli;
  11. class MysqlObject extends Mysqli implements PoolObjectInterface
  12. {
  13. /**
  14. * 释放对象时调用
  15. * gc
  16. * @author Tioncico
  17. * Time: 20:49
  18. */
  19. function gc()
  20. {
  21. // 重置为初始状态
  22. $this->resetDbStatus();
  23. // 关闭数据库连接
  24. $this->getMysqlClient()->close();
  25. }
  26. /**
  27. * 回收对象时会调用
  28. * objectRestore
  29. * @author Tioncico
  30. * Time: 20:49
  31. */
  32. function objectRestore()
  33. {
  34. // 重置为初始状态
  35. $this->resetDbStatus();
  36. }
  37. /**
  38. * 每个链接使用之前 都会调用此方法 请返回 true / false
  39. * 返回false时PoolManager会回收该链接 并重新进入获取链接流程
  40. * @return bool 返回 true 表示该链接可用 false 表示该链接已不可用 需要回收
  41. */
  42. function beforeUse(): bool
  43. {
  44. // 此处可以进行链接是否断线的判断 使用不同的数据库操作类时可以根据自己情况修改
  45. return $this->getMysqlClient()->connected;
  46. }
  47. }

连接池配置

连接池有以下配置,可通过PoolConf.php进行设置和查看

  1. <?php
  2. $intervalCheckTime = 30*1000;//定时验证对象是否可用以及保持最小连接的间隔时间
  3. $maxIdleTime = 15;//最大存活时间,超出则会每$intervalCheckTime/1000秒被释放
  4. $maxObjectNum = 20;//最大创建数量
  5. $minObjectNum = 5;//最小创建数量 最小创建数量不能大于等于最大创建数量,否则报错 每$intervalCheckTime/1000秒去判断最小连接数
  6. $getObjectTimeout = 3.0;
  7. $extraConf = [];//连接池对象获取连接对象时的等待时间

具体使用例子:

1:通过上面的2个接口例子,实现连接池对象连接对象

2:在EasySwooleEvent.php的initialize方法中注册连接池对象(注意命名空间,新版本可以无需注册,自动注册)

  1. // 注册mysql数据库连接池
  2. PoolManager::getInstance()->register(MysqlPool::class,Config::getInstance()->getConf('MYSQL.POOL_MAX_NUM'));
  3. //注册之后会返回conf配置,可继续配置,如果返回null代表注册失败

可通过register返回的PoolConf对象去配置其他参数

3:在worker(http控制器)进程调用(注意命名空间):

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Tioncico
  5. * Date: 2019/3/5 0005
  6. * Time: 21:07
  7. */
  8. namespace App\HttpController;
  9. use App\Utility\Pool\MysqlPool;
  10. use EasySwoole\Component\Pool\PoolManager;
  11. use EasySwoole\Http\AbstractInterface\Controller;
  12. class Index extends Controller
  13. {
  14. function index()
  15. {
  16. $db = PoolManager::getInstance()->getPool(MysqlPool::class)->getObj();
  17. $data = $db->get('test');
  18. //使用完毕需要回收
  19. PoolManager::getInstance()->getPool(MysqlPool::class)->recycleObj($db);
  20. $this->response()->write(json_encode($data));
  21. // TODO: Implement index() method.
  22. }
  23. }

直接getobj时,可能会出现没有连接(返回null)的情况,需要增加判断

无需注册

PoolManager中的getPool方法中,实现了对连接池的自动注册
当用户没有注册连接池时,直接getPool可直接自动注册并使用连接,例如上面的控制器用法:

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Tioncico
  5. * Date: 2019/3/5 0005
  6. * Time: 21:07
  7. */
  8. namespace App\HttpController;
  9. use App\Utility\Pool\MysqlPool;
  10. use EasySwoole\Component\Pool\PoolManager;
  11. use EasySwoole\Http\AbstractInterface\Controller;
  12. class Index extends Controller
  13. {
  14. function index()
  15. {
  16. $db = PoolManager::getInstance()->getPool(MysqlPool::class)->getObj();//直接使用无需注册
  17. $data = $db->get('test');
  18. //使用完毕需要回收
  19. PoolManager::getInstance()->getPool(MysqlPool::class)->recycleObj($db);
  20. $this->response()->write(json_encode($data));
  21. // TODO: Implement index() method.
  22. }
  23. }

自动回收

AbstractPool中,use了EasySwoole\Component\Pool\TraitInvokerinvoke方法,通过invoke方法,可直接在闭包中操作连接池连接,执行完自动回收,例如:

  1. <?php
  2. function poolInvoke(){
  3. $data = MysqlPool::invoke(function ( MysqlObject $db){
  4. $data = $db->get('test');
  5. return $data;
  6. });
  7. $this->response()->write(json_encode($data));
  8. }

异常拦截,当invoke调用,内部发生(连接不够,连接对象错误)等异常情况时,会抛出PoolEmpty和PoolException,可在控制器基类拦截或直接忽略,EasySwoole内部有做异常拦截处理,将直接拦截并返回错误到前端.

AbstractPool中,use了EasySwoole\Component\Pool\TraitInvokerdefer方法,通过defer方法,在当前协程执行完毕时,将自动回收连接,但不适用于匿名连接池,实现代码:

  1. <?php
  2. public static function defer($timeout = null)
  3. {
  4. $key = md5(static::class);
  5. $obj = ContextManager::getInstance()->get($key);
  6. if($obj){
  7. return $obj;
  8. }else{
  9. $pool = PoolManager::getInstance()->getPool(static::class);
  10. if($pool instanceof AbstractPool){
  11. $obj = $pool->getObj($timeout);
  12. if($obj){
  13. Coroutine::defer(function ()use($pool,$obj){
  14. $pool->recycleObj($obj);
  15. });
  16. ContextManager::getInstance()->set($key,$obj);
  17. return $obj;
  18. }else{
  19. throw new PoolEmpty(static::class." pool is empty");
  20. }
  21. }else{
  22. throw new PoolException(static::class." convert to pool error");
  23. }
  24. }
  25. }

预创建连接/热启动

在之前情况时,如果你重启EasySwoole,由于连接池对象是懒惰加载(只在调用时才创建连接),会导致当在一启动EasySwoole,访问量却很大时造成瞬间过大的压力,所以EasySwoole连接池组件提供了热启动.
EasySwooleEvent文件,mainServerCreate事件中增加onWorkerStart回调事件中预热启动:

  1. <?php
  2. //注册onWorkerStart回调事件
  3. public static function mainServerCreate(EventRegister $register)
  4. {
  5. $register->add($register::onWorkerStart, function (\swoole_server $server, int $workerId) {
  6. if ($server->taskworker == false) {
  7. //每个worker进程都预创建连接
  8. PoolManager::getInstance()->getPool(MysqlPool::class)->preLoad(5);//最小创建数量
  9. }
  10. });
  11. }

当连接池对象被实例化之后,每隔30秒($intervalCheckTime默认值)会将15秒($maxIdleTime默认值)未使用的连接彻底释放,并执行一次keepMin方法重新创建5个($minObjectNum默认值)连接对象.确保连接对象不被超时自动关闭

创建匿名连接池

当你不想新建文件实现 连接池 或者不想实现 连接池对象时,可通过registerAnonymous创建匿名连接池,例如:

EasySwooleEvent.php的initialize方法中注册连接池对象

  1. <?php
  2. PoolManager::getInstance()->registerAnonymous('mysql',function (){
  3. $conf = \EasySwoole\EasySwoole\Config::getInstance()->getConf("MYSQL");
  4. $dbConf = new Config($conf);
  5. return new Mysqli($dbConf);
  6. });

在控制器中使用:

  1. <?php
  2. $db = PoolManager::getInstance()->getPool('mysql')->getObj();
  3. $data = $db->get('test');
  4. $db->resetDbStatus();//重置为初始状态,否则回收之后会出问题
  5. PoolManager::getInstance()->getPool('mysql')->recycleObj($db);
  6. $this->response()->write(json_encode($data));

>