首页 / 资讯中心 / 文章详情

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

Redis 从 0 到 1(二十五):连接池又打满了——PoolSize 公式、泄漏排查与 PoolStats ★ FEATURED ARTICLE
我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录Redis 从 0 到 1二十五连接池又打满了——PoolSize 公式、泄漏排查与 PoolStats一、现象timeout 就加池加完还是 timeout二、PoolSize 计算公式收藏点 1三、用 PoolStats 的 4 个指标判断病因收藏点 2四、Go 实操PoolStats 监控与典型泄漏收藏点 3五、服务端配套配置收藏点 4六、排查步骤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 个指标判断病因收藏点 2go-redis 暴露rdb.PoolStats()4 个关键指标指标含义什么时候报警Timeouts从池取连接超时次数0 就说明池不够或泄漏这是症状指标TotalConnsvsPoolSize当前连接数/上限TotalConns 长期顶满 PoolSize 池满Hits/(HitsMisses)池命中率90% 说明连接复用差频繁建连StaleConns被服务端断掉的死连接持续增长说明配平没做好第 2 篇判断逻辑Timeouts涨 TotalConns顶满 慢查询多 →池不小是命令慢去第 21 篇加池无效Timeouts涨 TotalConns顶满 Hits高 命令很快 →真池小按公式加Timeouts涨 TotalConns波动大、新连接创建频繁 →有地方没复用连接代码里反复 NewClient。四、Go 实操PoolStats 监控与典型泄漏收藏点 3监控上报Prometheus 格式第 35 篇接采集packagemainimport(contextfmttimegithub.com/redis/go-redis/v9redis-0to1/redisx)funcReportPoolStats(rdb*redis.Client){t:time.NewTicker(30*time.Second)forranget.C{fmt.Println(redisx.PoolStats(rdb))// 生产替换为 metrics 上报}}典型泄漏场景复盘——长事务占用连接// ❌ 错误示范一个 context 里串行执行 100 条命令每条 20ms// 高峰时这个请求独占 1 条连接 2 秒并发 50 就把 20 的池打满funcBadBatchGet(ctx context.Context,rdb*redis.Client,keys[]string){for_,k:rangekeys{rdb.Get(ctx,k)// 逐条 RTT第 24 篇的反面教材}}// ✅ 修法一Pipeline 把 2 秒压缩成 20ms连接占用降 100 倍// ✅ 修法二批量接口加超时上限防止个别请求拖死连接池funcGoodBatchGet(ctx context.Context,rdb*redis.Client,keys[]string){ctx,cancel:context.WithTimeout(ctx,200*time.Millisecond)// 兜底defercancel()// ... Pipeline 实现见第 24 篇}第 2 篇的 redisx 封装里已经带PoolStats()输出——现在你知道它是为哪一篇准备的了。五、服务端配套配置收藏点 4# redis.conf maxclients 10000 # 天花板按实例数 × PoolSize 运维余量核算 timeout 300 # 空闲断开客户端 ConnMaxIdleTime 要比它小第 2 篇 tcp-keepalive 60实例数 × PoolSize 的账要定期重算服务从 3 个 pod 扩到 30 个池配置不变服务端连接数就涨了 10 倍——这就是第 34 篇《maxclients 打满但 QPS 只有 500》的事故现场预告。六、排查步骤connection pool timeoutPoolStats()抓指标Timeouts/TotalConns/Hits三对照第三节判断树命令慢不慢slowlog 有货 → 先治慢命令代码有没有逐条 RTT 循环有 → 改 Pipeline真池小按公式重算 PoolSize同时检查服务端 maxclients 余量改完压测回归Timeouts归零、TotalConns稳定在公式值附近。七、总结PoolSize 按QPS × 单命令耗时 × 1.3算拍脑袋加池可能适得其反PoolStats 四指标判断树Timeouts 是症状Hits/TotalConns 是病因连接泄漏的头号形态是逐条 RTT 的长事务解法在第 24 篇服务端 maxclients 要按实例数 × PoolSize定期核算。下一篇预告连接稳了下一篇往内存里看——下一篇《INFO memory 里 8 个关键数字——内存构成、编码转换listpack与容量规划》我们搞清楚 Redis 的内存都花在哪了。
阅读完成 · 觉得有帮助?
咨询建站