TP6万级并发Redis连接耗尽源于连接未复用、连接池未生效、实例乱新建;修复需三步:启用redis_pool驱动、校准池参数(max_active=60等)、全局禁用new Redis并强制单例。

TP6在一万并发下Redis连接耗尽,不是扛不住流量,而是连接没复用、池没生效、实例乱新建——三者叠加导致每请求建一个连接,瞬间打爆Redis的maxclients限制。修复不靠扩容Redis,而靠三步精准落地:驱动切换、池参数校准、单例兜底。
一、确认并启用redis_pool驱动
TP6默认type => 'redis'走的是直连模式,每次Cache::get()或Redis::set()都新建TCP连接,哪怕你配了pool字段也完全无效。
- 打开
config/cache.php,把'type' => 'redis'改为'type' => 'redis_pool' - 若提示“未注册驱动”,在
app/common.php末尾追加:Cache::extend('redis_pool', \think\cache\driver\RedisPool::class); - 删掉
stores.redis下所有host/port/database等旧字段,统一归入redis_pool配置块中
二、设置安全且够用的池参数
池子太大浪费内存,太小扛不住峰值;超时不设会卡死整个PHP-FPM进程。关键参数必须显式声明:
-
max_active => 60:理论支撑QPS 2000以内读密集型业务(每个连接约128KB内存) -
min_idle => 5:保底常驻连接,避免冷启动抖动 -
max_wait => 3000:获取连接最大等待3秒,超时抛异常而非无限阻塞 -
timeout => 2000:连接建立超时2秒,防DNS卡死
完整示例(放入config/cache.php的stores.redis_pool中):
立即学习“PHP免费学习笔记(深入)”;
'redis_pool' => ['type' => 'redis_pool',
'host' => '10.0.1.5', // 强烈建议用内网IP,避开DNS解析风险
'port' => 6379,
'password' => '',
'database' => 0,
'pool' => [
'max_idle' => 20,
'min_idle' => 5,
'max_active' => 60,
'max_wait' => 3000,
'timeout' => 2000
]
],
三、杜绝new Redis实例 + 强制全局单例
控制器或服务类里写new \think\cache\driver\Redis()是连接泄漏头号元凶——它绕过所有池配置,每次都是全新连接,且不自动释放。
- 全局搜索项目代码,删除所有
new Redis、new \Redis、new \think\cache\driver\Redis调用 - 如需手动操作Redis,统一使用
Cache::store('redis_pool')获取实例 - 对Redisson等第三方客户端,必须加
volatile static双重校验锁实现单例,否则每个请求都新建Client
四、验证是否真正生效
改完别急着压测,先做三件事确认修复到位:
- 执行
redis-cli info clients | grep connected_clients,观察连接数是否稳定在60–80之间(非持续飙升) - 在代码中临时加日志:
echo "PID: ".getmypid()." | Redis instance ID: ".spl_object_id(Cache::store('redis_pool'));,多请求下ID应一致 - 模拟高并发(如ab -n 5000 -c 200 http://your-api.com/test),看错误日志是否还有
Could not get a resource from the pool



















