本文目录导读:

设计PHP连接池是一个常见的需求,尤其是在高并发场景下,让我为你详细讲解一下。
为什么要用连接池
PHP每次请求结束后,传统的mysql连接会被销毁,导致频繁建立和断开连接,消耗大量资源,连接池的核心思想是复用连接,减少连接建立的开销。
几种实现方案
使用 Swoole 的协程连接池(推荐)
<?php
use Swoole\Coroutine\Channel;
use Swoole\Coroutine;
class MySQLPool
{
private $pool;
private $config;
private $maxSize;
private $minSize;
private $currentSize = 0;
public function __construct($config, $maxSize = 10, $minSize = 2)
{
$this->config = $config;
$this->maxSize = $maxSize;
$this->minSize = $minSize;
$this->pool = new Channel($maxSize);
// 初始化最小连接数
for ($i = 0; $i < $minSize; $i++) {
$this->createConnection();
}
}
private function createConnection()
{
if ($this->currentSize >= $this->maxSize) {
return false;
}
try {
$conn = new PDO(
"mysql:host={$this->config['host']};port={$this->config['port']};dbname={$this->config['database']};charset=utf8mb4",
$this->config['username'],
$this->config['password'],
[
PDO::ATTR_TIMEOUT => 5,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]
);
$this->currentSize++;
$this->pool->push($conn);
return true;
} catch (Exception $e) {
error_log("创建连接失败: " . $e->getMessage());
return false;
}
}
public function getConnection($timeout = 3)
{
// 如果池为空且未达到最大容量,创建新连接
if ($this->pool->isEmpty() && $this->currentSize < $this->maxSize) {
$this->createConnection();
}
$conn = $this->pool->pop($timeout);
if (!$conn) {
throw new RuntimeException("获取连接超时");
}
// 检查连接是否有效
if (!$this->isValid($conn)) {
$this->currentSize--;
return $this->getConnection($timeout);
}
return $conn;
}
public function releaseConnection($conn)
{
if ($conn) {
$this->pool->push($conn);
}
}
private function isValid($conn)
{
try {
return $conn->query("SELECT 1") !== false;
} catch (Exception $e) {
return false;
}
}
public function close()
{
while (!$this->pool->isEmpty()) {
$conn = $this->pool->pop();
$conn = null;
$this->currentSize--;
}
}
}
// 使用示例
class DB {
private static $pool;
public static function init($config)
{
self::$pool = new MySQLPool($config, 10, 2);
}
public static function query($sql, $params = [])
{
$conn = self::$pool->getConnection();
try {
$stmt = $conn->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} finally {
self::$pool->releaseConnection($conn);
}
}
}
使用 Redis 实现连接池(通过 Redis 存储连接)
<?php
class RedisConnPool
{
private $redis;
private $listKey;
private $config;
public function __construct($redis, $config)
{
$this->redis = $redis;
$this->config = $config;
$this->listKey = 'mysql_connection_pool';
}
public function getConnection()
{
// 从列表右侧获取连接
$connectionData = $this->redis->rPop($this->listKey);
if (!$connectionData) {
// 没有可用连接,创建新连接
$conn = $this->createConnection();
} else {
$conn = unserialize($connectionData);
// 验证连接是否有效
if (!$this->isValid($conn)) {
return $this->getConnection();
}
}
return $conn;
}
public function returnConnection($conn)
{
// 序列化连接信息并放入列表
$this->redis->lPush($this->listKey, serialize($conn));
}
private function createConnection()
{
$conn = new mysqli(
$this->config['host'],
$this->config['username'],
$this->config['password'],
$this->config['database'],
$this->config['port']
);
return $conn;
}
private function isValid($conn)
{
return $conn->ping();
}
}
使用连接池框架
<?php
// 使用 pfdbc/pfdbc 库(PHP 5.6+)或 mysqli_pool 扩展
// 1. 安装 pfdbc
composer require pfdbc/pfdbc
// 2. 使用示例
$config = new \PF\DB\Config([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'test',
'username' => 'root',
'password' => 'password',
'pool' => [
'max_connections' => 10,
'min_connections' => 2,
'wait_timeout' => 3
]
]);
$pool = new \PF\DB\Pool($config);
$conn = $pool->getConnection();
try {
$result = $conn->query("SELECT * FROM users");
$users = $result->fetchAll();
} finally {
$pool->releaseConnection($conn);
}
连接池设计要点
核心参数设计
class ConnectionPoolConfig
{
// 最小连接数
public $minSize = 2;
// 最大连接数
public $maxSize = 10;
// 最大空闲时间(秒)
public $idleTimeout = 60;
// 连接最大存活时间(秒)
public $maxLifetime = 3600;
// 获取连接最大等待时间(秒)
public $waitTimeout = 3;
// 连接测试查询
public $testQuery = "SELECT 1";
}
连接监控与健康检查
class PoolMonitor
{
private $pool;
private $metrics = [
'total_created' => 0,
'total_destroyed' => 0,
'total_requests' => 0,
'total_hits' => 0, // 复用率
'total_misses' => 0 // 新创建
];
public function recordHit() { $this->metrics['total_hits']++; }
public function recordMiss() { $this->metrics['total_misses']++; }
public function recordCreated() { $this->metrics['total_created']++; }
public function recordDestroyed() { $this->metrics['total_destroyed']++; }
public function getMetrics()
{
return array_merge($this->metrics, [
'hit_rate' => $this->metrics['total_hits'] / $this->metrics['total_requests'],
'current_pool_size' => count($this->pool->getConnections())
]);
}
}
防止连接泄漏
class SafeConnection
{
private $pool;
private $conn;
private $leaseTime;
public function __construct($pool, $conn)
{
$this->pool = $pool;
$this->conn = $conn;
$this->leaseTime = time();
}
public function __destruct()
{
// 如果使用时间超过设定值,强制回收
if (time() - $this->leaseTime > 300) { // 5分钟
$this->close();
} else {
$this->pool->release($this->conn);
}
}
private function close()
{
$this->pool->discard($this->conn);
}
}
注意事项
- 事务处理:使用连接池时,确保事务结束后及时释放连接
- 异常处理:连接失败时要有重试机制,但避免无限重试
- 超时控制:获取连接时要设置超时时间,避免请求堆积
- 内存管理:定期清理空闲过久的连接,避免内存泄漏
- 并发安全:多进程环境要使用共享内存或外部存储实现真正的连接池
性能优化建议
// 使用 SQL 预编译缓存
$stmt->bindParam(':id', $id);
$stmt->execute();
// 批量操作
$pdo->beginTransaction();
try {
// 多个操作
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
选择合适的连接池方案取决于你的应用场景:
- Swoole常驻内存:使用协程连接池(推荐)
- PHP-FPM架构:考虑使用Redis等外部存储实现共享
- 低频场景:直接使用PDO连接,不用连接池
在实际生产环境中,建议结合监控系统实时观察连接池状态,根据实际负载动态调整配置。