TP6.0项目Redis连接数飙升至几千,根本原因是默认不启用连接池复用,每次Cache::get()或Redis::command()都新建连接,尤其在未配置pool参数或手动new Redis实例时;需在config/cache.php中显式配置stores.redis.pool并设min_idle>0、max_active合理,且避免业务代码中硬编码new实例。

为什么 TP6.0 项目里 Redis 连接数会突然飙升到几千?
不是 Redis 本身扛不住,而是 ThinkPHP 6.0 默认不复用连接池,每次 Cache::get() 或 Redis::command() 都可能新建连接——尤其在未显式配置连接池、或用了 new \think\cache\driver\Redis() 手动实例时。TP6 的 think-cache 扩展默认走的是「单连接直连」模式,maxclients 没到上限前,连接数就先被 PHP-FPM 进程数 × 并发请求数撑爆了。
常见现象:redis-cli info clients 显示 connected_clients 持续 >500,且 client_longest_output_list 和 client_biggest_input_buf 异常高;服务器 netstat -an | grep :6379 | wc -l 返回值远超预期。
- 检查是否在控制器/服务中反复 new Redis 实例(如
new \think\cache\driver\Redis(...)),这是最典型的泄漏源头 - 确认
config/cache.php中的default驱动是否为redis,且stores.redis下没配pool参数 - TP6.0 默认不启用连接池复用,哪怕你写了
'host' => '127.0.0.1',也不代表它会复用连接
TP6.0 怎么强制启用 Redis 连接池复用?
必须显式声明连接池,并让所有缓存操作共享同一池实例。TP6 不像 Laravel 或 Spring Boot 那样自动管理单例连接池,得靠配置+手动接管。
在 config/cache.php 的 stores.redis 节点下,加 pool 子项:
return [
'stores' => [
'redis' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'database' => 0,
'timeout' => 3,
'pool' => [
'max_idle_time' => 60, // 秒,空闲连接最大存活时间
'max_active' => 32, // 最大活跃连接数(不是总连接数)
'max_wait' => 3000,// 毫秒,获取连接最长等待
'min_idle' => 4, // 最小空闲连接数
],
],
],
];
关键点:
-
max_active不是越大越好:TP6 的 Redis 驱动底层调用 phpredis,每个连接独占一个 PHP 进程资源;设成 100 会让 20 个 PHP-FPM 进程各持 100 连接,瞬间打满 Redismaxclients -
min_idle必须大于 0,否则池子启动后不预热,首请求仍要建新连 - 若用
phpredis扩展(推荐),确保extension=redis.so已启用;若用predis,连接池行为不一致,pool参数可能被忽略
TP6.0 Redis 连接卡死、超时、TIME_WAIT 堆积怎么调?
连接池开了,但依然报 Could not get a resource from the pool 或大量 TIME_WAIT,说明超时链路没对齐。TP6 的 timeout 是三层嵌套:DNS 解析 → TCP 建连 → Redis 协议读写,任一环卡住都会拖垮整个池。
必须统一控制这三段:
-
timeout(配置项):对应 phpredis 的connect_timeout和read_write_timeout,建议设为3(秒),别用默认0(无限等待) -
pool.max_wait:获取连接的等待上限,设为3000毫秒,避免线程挂起太久 - 服务端
timeout参数(Redis 配置里的timeout):必须小于客户端timeout,建议设为30秒,否则空闲连接长期挂着,池子里的连接无法回收 - Linux 内核参数:如果
netstat -an | grep TIME_WAIT | wc -l> 5000,需调大/proc/sys/net/ipv4/ip_local_port_range和net.ipv4.tcp_fin_timeout
验证方式:用 CLIENT LIST 查 idle 字段,若大量连接 idle > 30 秒,说明服务端 timeout 没生效或客户端没及时归还连接。
为什么改了配置还是有连接泄漏?检查这几处硬编码
TP6 项目里最容易被忽略的泄漏点,不在配置文件,而在业务代码里直接 new 实例、或在闭包/命令行任务中绕过容器注册。
- 命令行脚本(
php think xxx)里手写$redis = new \Redis(); $redis->connect(...)—— 这种连接永远不会进池,也不会被 GC 自动释放 - 模型事件或中间件里,用
\think\Cache::init(['type'=>'redis', ...])重复初始化驱动,每次 init 都新建一套连接池 - 使用
\think\facade\Cache时,误以为它是单例,其实底层每次调用都可能触发新连接(除非你提前调过Cache::store('redis')并复用返回值) - 协程环境(如 Swoole + TP6)下,未设置
pool.max_idle_time,导致连接在 worker 生命周期内一直不释放
最稳妥的做法:全局只通过 Cache::store('redis') 获取一次驱动实例,所有业务逻辑复用它返回的对象;命令行任务改用 think-queue 的内置 Redis 驱动,别自己 new。











