├── MySQLPool.php ├── README.md └── img ├── ab.png ├── abafter.png └── abbefore.png /MySQLPool.php: -------------------------------------------------------------------------------- 1 | $config) { 29 | self::$spareConns[$name] = []; 30 | self::$busyConns[$name] = []; 31 | self::$pendingFetchCount[$name] = 0; 32 | self::$resumeFetchCount[$name] = 0; 33 | if ($config['maxSpareConns'] <= 0 || $config['maxConns'] <= 0) { 34 | throw new MySQLException("Invalid maxSpareConns or maxConns in {$name}"); 35 | } 36 | } 37 | self::$init = true; 38 | } 39 | 40 | static public function recycle(\Swoole\Coroutine\MySQL $conn) 41 | { 42 | if (!self::$init) { 43 | throw new MySQLException('Should call MySQLPool::init.'); 44 | } 45 | 46 | $id = spl_object_hash($conn); 47 | $connName = self::$connsNameMap[$id]; 48 | if (isset(self::$busyConns[$connName][$id])) { 49 | unset(self::$busyConns[$connName][$id]); 50 | } else { 51 | throw new MySQLException('Unknow MySQL connection.'); 52 | } 53 | 54 | $connsPool = &self::$spareConns[$connName]; 55 | if ($conn->connected) { 56 | if (count($connsPool) >= self::$connsConfig[$connName]['maxSpareConns']) { 57 | $conn->close(); 58 | } else { 59 | $connsPool[] = $conn; 60 | if (self::$pendingFetchCount[$connName] > 0) { 61 | self::$resumeFetchCount[$connName]++; 62 | self::$pendingFetchCount[$connName]--; 63 | \Swoole\Coroutine::resume('MySQLPool::' . $connName); 64 | } 65 | return; 66 | } 67 | } 68 | 69 | unset(self::$connsNameMap[$id]); 70 | } 71 | 72 | static public function fetch($connName) 73 | { 74 | if (!self::$init) { 75 | throw new MySQLException('Should call MySQLPool::init!'); 76 | } 77 | 78 | if (!isset(self::$connsConfig[$connName])) { 79 | throw new MySQLException("Unvalid connName: {$connName}."); 80 | } 81 | 82 | $connsPool = &self::$spareConns[$connName]; 83 | if (!empty($connsPool) && count($connsPool) > self::$resumeFetchCount[$connName]) { 84 | $conn = array_pop($connsPool); 85 | if ($conn->connected) { 86 | self::$busyConns[$connName][spl_object_hash($conn)] = $conn; 87 | return $conn; 88 | } 89 | } 90 | 91 | if (count(self::$busyConns[$connName]) + count($connsPool) == self::$connsConfig[$connName]['maxConns']) { 92 | self::$pendingFetchCount[$connName]++; 93 | if (\Swoole\Coroutine::suspend('MySQLPool::' . $connName) == false) { 94 | self::$pendingFetchCount[$connName]--; 95 | throw new MySQLException('Reach max connections! Cann\'t pending fetch!'); 96 | } 97 | self::$resumeFetchCount[$connName]--; 98 | if (!empty($connsPool)) { 99 | $conn = array_pop($connsPool); 100 | if ($conn->connected) { 101 | self::$busyConns[$connName][spl_object_hash($conn)] = $conn; 102 | return $conn; 103 | } 104 | } else { 105 | return false;//should not happen 106 | } 107 | } 108 | 109 | $conn = new \Swoole\Coroutine\MySQL(); 110 | $id = spl_object_hash($conn); 111 | self::$connsNameMap[$id] = $connName; 112 | self::$busyConns[$connName][$id] = $conn; 113 | if ($conn->connect(self::$connsConfig[$connName]['serverInfo']) == false) { 114 | unset(self::$busyConns[$connName][$id]); 115 | unset(self::$connsNameMap[$id]); 116 | throw new MySQLException('Cann\'t connect to MySQL server: ' . json_encode(self::$connsConfig[$connName]['serverInfo'])); 117 | } 118 | 119 | return $conn; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 基于Swoole2协程特性实现的MySQL连接池 2 | - - - 3 | 使用Swoole2的\Swoole\Coroutine\MySQL创建连接,通过静态类和静态成员属性维护连接池,不同协程可以共享该连接池。 4 | 5 | 排队机制(先进先出)使用协程的特殊功能实现: 6 | 7 | * `\Swoole\Coroutine::resume($name)`:从$name队列中恢复一个挂起的协程执行; 8 | * `\Swoole\Coroutine::suspend($name)`:将当前协程挂起到$name队列上。 9 | 10 | ### 限制 11 | 12 | * 每个worker都有各自的MySQL连接池,且不同worker之间无法共享彼此的MySQL连接池; 13 | * 可能存在各个worker进程连接池利用率不同(依赖业务实现)。 14 | 15 | ### 优点 16 | 17 | * 与独立的连接池(worker进程间可共享的连接池实现)对比,无进程间通信开销; 18 | * 独立的连接池需要增加运维成本。 19 | 20 | ### 使用方法 21 | 22 | ```php 23 | /** 24 | * 初始化连接池 25 | * 26 | * @param array $connsConfig 配置数组 27 | * [ 28 | * 'connName1' => [ 29 | * 'serverInfo' => ['host' => '127.0.0.1', 'user' => 'test', 'password' => 'pass', 'database' => 'tt', 'charset' => 'utf8'], //\Swoole\Coroutine\MySQL的connect参数 30 | * 'maxSpareConns' => 5, //最大空闲连接数 31 | * 'maxConns' => 10, //最大连接数 32 | * ], 33 | * 'connName2' => [ 34 | * 'serverInfo' => ['host' => '127.0.0.2', 'user' => 'test', 'password' => 'pass', 'database' => 'tt', 'charset' => 'utf8'], //\Swoole\Coroutine\MySQL的connect参数 35 | * 'maxSpareConns' => 5, //最大空闲连接数 36 | * 'maxConns' => 10, //最大连接数 37 | * ], 38 | * ] 39 | */ 40 | Swoole\Coroutine\Pool\MySQLPool::init(array $connsConfig) 41 | 42 | /** 43 | * 回收连接,该连接必须是从连接池中获取的连接 44 | * 45 | * @param \Swoole\Coroutine\MySQL $conn 从连接池中获取的连接 46 | */ 47 | Swoole\Coroutine\Pool\MySQLPool::recycle(\Swoole\Coroutine\MySQL $conn) 48 | 49 | /** 50 | * 从连接池中获取一条连接 51 | * 52 | * @param string $connName init时配置的连接,根据连接名称获取对应的连接 53 | * @return \Swoole\Coroutine\MySQL 返回一个连接实例 54 | */ 55 | Swoole\Coroutine\Pool\MySQLPool::fetch($connName) 56 | ``` 57 | 58 | ### 使用示例 59 | 60 | ```php 61 | set([ 67 | 'worker_num' => 1, 68 | ]); 69 | $server->on('Request', function($request, $response) { 70 | MySQLPool::init([ 71 | 'test' => [ 72 | 'serverInfo' => ['host' => '192.168.244.128', 'user' => 'mha_manager', 'password' => 'mhapass', 'database' => 'tt', 'charset' => 'utf8'], 73 | 'maxSpareConns' => 5, 74 | 'maxConns' => 10 75 | ], 76 | ]); 77 | $swoole_mysql = MySQLPool::fetch('test'); 78 | $ret = $swoole_mysql->query('select sleep(1)'); 79 | MySQLPool::recycle($swoole_mysql); 80 | $response->end('Test End'); 81 | }); 82 | $server->start(); 83 | ``` 84 | 85 | 压测命令:`ab -c 20 -n 100 -s 100 http://127.0.0.1:9502/`,20并发,共100个请求。 86 | 87 | 压测结果: 88 | 89 | ![ab](img/ab.png) 90 | 91 | 压测时连接情况,20并发,最大连接数限制为10,所以最多只会与数据库建立10条连接: 92 | 93 | ![ab](img/abbefore.png) 94 | 95 | 压测后连接情况,设置了最大空闲连接数为5,所以现在没有客户端请求时,空闲连接数维持为5: 96 | 97 | ![ab](img/abafter.png) 98 | 99 | 这里服务端只有1个worker进程在工作,完成100个请求,且每个请求的SQL查询是sleep 1秒,花了约11秒,如果是php-fpm+mysqli这样的模式,1个worker进程,那么得花100秒。 100 | 101 | 所以这正是协程的优势所在,利用非阻塞IO+协程切换,1个worker进程能同时处理多个客户端请求,大大提高了吞吐量。 102 | -------------------------------------------------------------------------------- /img/ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangxikun/swoole2-mysqlpool/e9bbbd2a95e9c5c3ad0fddc04c190650aa2788c8/img/ab.png -------------------------------------------------------------------------------- /img/abafter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangxikun/swoole2-mysqlpool/e9bbbd2a95e9c5c3ad0fddc04c190650aa2788c8/img/abafter.png -------------------------------------------------------------------------------- /img/abbefore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangxikun/swoole2-mysqlpool/e9bbbd2a95e9c5c3ad0fddc04c190650aa2788c8/img/abbefore.png --------------------------------------------------------------------------------