Redis 从 0 到 1(二十五):连接池又打满了——PoolSize 公式、泄漏排查与 PoolStats

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [Redis 从 0 到 1(二十五):连接池又打满了------PoolSize 公式、泄漏排查与 PoolStats](#Redis 从 0 到 1(二十五):连接池又打满了——PoolSize 公式、泄漏排查与 PoolStats)
    • [一、现象:timeout 就加池,加完还是 timeout](#一、现象:timeout 就加池,加完还是 timeout)
    • [二、PoolSize 计算公式(收藏点 1)](#二、PoolSize 计算公式(收藏点 1))
    • [三、用 PoolStats 的 4 个指标判断病因(收藏点 2)](#三、用 PoolStats 的 4 个指标判断病因(收藏点 2))
    • [四、Go 实操:PoolStats 监控与典型泄漏(收藏点 3)](#四、Go 实操:PoolStats 监控与典型泄漏(收藏点 3))
    • [五、服务端配套配置(收藏点 4)](#五、服务端配套配置(收藏点 4))
    • [六、排查步骤:connection pool timeout](#六、排查步骤:connection pool timeout)
    • 七、总结

Redis 从 0 到 1(二十五):连接池又打满了------PoolSize 公式、泄漏排查与 PoolStats

上一篇《Redis 从 0 到 1(二十四):1 万次 GET 从 800ms 降到 9ms------Pipeline 的 Go 压测》我们把 1 万次 RTT 压成 50 次,链接:https://blog.csdn.net/2501_92769340/article/details/167399903 。

本篇处理优化的"最后一公里":连接池。流量一涨就 connection pool timeout,加 PoolSize 真的有用吗?

一、现象:timeout 就加池,加完还是 timeout

服务 QPS 涨到 3000 后开始报 connection pool timeout。第一反应该是把 PoolSize 从 20 调到 50------没用,又调到 100------报得更频繁了。最后定位到真相:不是池太小,是连接泄漏。有段代码拿到连接执行命令后,在错误分支提前 return,连接没归还(虽然 go-redis 用完后自动回池,但阻塞在慢命令上的连接占用时间被拉长)。

加池为什么可能更糟:池越大,慢命令并发越多,Redis 单线程排队越长,单命令 RTT 越高 → 每条连接占用更久 → 更需要连接......恶性循环。池大小必须按公式来。

二、PoolSize 计算公式(收藏点 1)

复制代码
所需连接数 ≈ 峰值 QPS × 平均单命令耗时(秒)× 安全系数(1.2~1.5)

例:3000 QPS,平均命令耗时 2ms(含 RTT)
    → 3000 × 0.002 × 1.3 ≈ 8 个连接

对照直觉检查:如果你的 PoolSize 算出 8,实际配了 100,多出来的 92 个连接只会制造"能发更多并发命令"的假象,把 Redis 的排队延迟推高。

还要同时看服务端:maxclients(默认 10000)减去保留(约 32),是所有客户端加起来的天花板。几十个微服务实例 × 每实例 100 连接 = 可能顶到 maxclients,出现 ERR max number of clients reached(第 34 篇专门排查)。

三、用 PoolStats 的 4 个指标判断病因(收藏点 2)

go-redis 暴露 rdb.PoolStats(),4 个关键指标:

指标 含义 什么时候报警
Timeouts 从池取连接超时次数 >0 就说明池不够或泄漏;这是症状指标
TotalConns vs PoolSize 当前连接数/上限 TotalConns 长期顶满 PoolSize = 池满
Hits/(Hits+Misses) 池命中率 <90% 说明连接复用差(频繁建连)
StaleConns 被服务端断掉的死连接 持续增长说明配平没做好(第 2 篇)

判断逻辑:

  • Timeouts 涨 + TotalConns 顶满 + 慢查询多 → 池不小,是命令慢(去第 21 篇),加池无效;
  • Timeouts 涨 + TotalConns 顶满 + Hits 高 + 命令很快 → 真池小,按公式加;
  • Timeouts 涨 + TotalConns 波动大、新连接创建频繁 → 有地方没复用连接(代码里反复 NewClient?)。

四、Go 实操:PoolStats 监控与典型泄漏(收藏点 3)

监控上报(Prometheus 格式,第 35 篇接采集):

go 复制代码
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
	"redis-0to1/redisx"
)

func ReportPoolStats(rdb *redis.Client) {
	t := time.NewTicker(30 * time.Second)
	for range t.C {
		fmt.Println(redisx.PoolStats(rdb)) // 生产替换为 metrics 上报
	}
}

典型泄漏场景复盘------长事务占用连接:

go 复制代码
// ❌ 错误示范:一个 context 里串行执行 100 条命令,每条 20ms
// 高峰时这个请求独占 1 条连接 2 秒;并发 50 就把 20 的池打满
func BadBatchGet(ctx context.Context, rdb *redis.Client, keys []string) {
	for _, k := range keys {
		rdb.Get(ctx, k) // 逐条 RTT(第 24 篇的反面教材)
	}
}

// ✅ 修法一:Pipeline 把 2 秒压缩成 20ms,连接占用降 100 倍
// ✅ 修法二:批量接口加超时上限,防止个别请求拖死连接池
func GoodBatchGet(ctx context.Context, rdb *redis.Client, keys []string) {
	ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) // 兜底
	defer cancel()
	// ... Pipeline 实现见第 24 篇
}

第 2 篇的 redisx 封装里已经带 PoolStats() 输出------现在你知道它是为哪一篇准备的了。

五、服务端配套配置(收藏点 4)

conf 复制代码
# redis.conf
maxclients 10000        # 天花板,按"实例数 × PoolSize + 运维余量"核算
timeout 300             # 空闲断开(客户端 ConnMaxIdleTime 要比它小,第 2 篇)
tcp-keepalive 60

实例数 × PoolSize 的账要定期重算:服务从 3 个 pod 扩到 30 个,池配置不变,服务端连接数就涨了 10 倍------这就是第 34 篇《maxclients 打满但 QPS 只有 500》的事故现场预告。

六、排查步骤:connection pool timeout

  1. PoolStats() 抓指标:Timeouts/TotalConns/Hits 三对照(第三节判断树);
  2. 命令慢不慢:slowlog 有货 → 先治慢命令;
  3. 代码有没有逐条 RTT 循环:有 → 改 Pipeline;
  4. 真池小:按公式重算 PoolSize,同时检查服务端 maxclients 余量;
  5. 改完压测回归:Timeouts 归零、TotalConns 稳定在公式值附近。

七、总结

  1. PoolSize 按 QPS × 单命令耗时 × 1.3 算,拍脑袋加池可能适得其反;
  2. PoolStats 四指标判断树:Timeouts 是症状,Hits/TotalConns 是病因;
  3. 连接泄漏的头号形态是"逐条 RTT 的长事务",解法在第 24 篇;
  4. 服务端 maxclients 要按"实例数 × PoolSize"定期核算。

下一篇预告:连接稳了,下一篇往内存里看------下一篇《INFO memory 里 8 个关键数字------内存构成、编码转换(listpack)与容量规划》,我们搞清楚 Redis 的内存都花在哪了。

相关推荐
我不会起名字32227 分钟前
Redis 从 0 到 1(二十三):一个热 key 打到 10 万 QPS——hotkey 与本地缓存的 Go 实现
redis·缓存·go·qps·hotkey
IT规划师2 小时前
Redis 数据类型全解 - Bitmap 生产实战:把位视图讲准、用对、守住红线(六)
redis·.net
Fatbobman(东坡肘子)14 小时前
快速消失的硬盘容量 -- 肘子的 Swift 周报 #160
开发语言·人工智能·redis·ios·swiftui·iphone·swift
温暖小土15 小时前
在 CentOS 上安装 Redis Stack
linux·redis·centos
Mikko718 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
无言听风雨丶18 小时前
Redis 运维实战与排错
运维·数据库·redis
字节探索1 天前
Redis 分布式锁:从 SETNX 误删到看门狗自动续期的原理与实践
redis·分布式
真上帝的左手1 天前
30. 技术专题-‌应用框架组件-缓存-Redis
数据库·redis·分布式·缓存·中间件