当前位置: 首页 > news >正文

连接池设置的艺术:从一次“Threads_connected 超 10000”的线上告警说起

350 台应用服务器,2 个数据库集群,每个集群 6 个分库,每个分库minPoolSize=5……当这些数字叠加在一起时,每个数据库集群的Threads_connected轻松突破 10000,数据库瞬间进入高压模式。这次告警我作为稳定性负责人临时接手排查——服务并非我设计,日常也非我维护,但正是这种“旁观者”视角,让我看清了连接池配置与分库数量之间被长期忽视的乘积炸弹。


一、事故现场还原

某天下午,监控系统突然爆出两条告警:

  • 数据库连接使用率超过 70%(两个 MySQL 集群各自Threads_connected均突破 10000)
  • 具体数值:集群 A 约 10,500,集群 B 约 10,200,双双超过预设的 10,000 告警阈值

当时我作为稳定性负责人被拉进紧急会议,开始排查这套我从未参与设计、日常也不负责维护的服务。初步了解架构后发现:应用使用了分库分表,数据访问层采用美团开源的 Zebra 数据中间件。Zebra 会为配置的每个物理数据库(每个分片)独立创建并维护一个连接池,应用服务器直连物理 DB 的数量等于分片数。

架构细节:2 个数据库集群(cluster_0 / cluster_1),每个集群下各有6 个物理数据库(db_0 ~ db_5)。Zebra 配置了这两组数据源,每个应用服务器启动后会建立2 × 6 = 12 个独立的 C3P0 连接池

每个连接池(C3P0)的配置为:

initialPoolSize=5 minPoolSize=5 # 关键!每个连接池启动后会至少保持 5 个空闲连接 maxPoolSize=30

数据库服务器配置:每个集群的数据库实例运行在独享容器中,规格为24核 / 140GB内存 / PCIE-SSD 2900G / 千兆网卡。这样的配置在业界已属中高端,但在 10,000+ 连接面前仍然捉襟见肘。

为什么告警值是 10,000,而不是350 × 6 × 30 = 63,000?因为连接池的minPoolSize=5意味着每个应用服务器对每个物理数据库会维持至少 5 个常驻连接。扩容后,连接池会立即按minPoolSize创建连接,于是实际已经建立的连接数为:

单个集群总连接数 = 应用实例数 × 该集群内物理 DB 数量 × minPoolSize
= 350 × 6 × 5 =10,500

maxPoolSize=30是峰值上限,但由于业务并发尚未打满,连接池实际保持在minPoolSize=5的水平。即便如此,10,500 已经远超 MySQL 集群常见的max_connections(通常设为 4000~8000),告警立即触发。而两个集群各自独立,因此同时收到两条“超过 10000”的告警。

如果把maxPoolSize=30用满,单个集群连接数将高达 63,000,那将是灾难性的——但正是minPoolSize这个“温和”的参数,已经让系统在扩容瞬间就突破了警戒线。


二、根本原因:分库分表下的“保底连接”爆炸 + 过度分片

很多团队在配置连接池时,只关注maxPoolSize,却忽略了minPoolSizeinitialPoolSize在多实例多分片场景下的乘积效应。通用公式有两个:

  • 保底连接数(稳定态)= 应用实例数 × 分片数 ×minPoolSize
  • 峰值连接数(极限)= 应用实例数 × 分片数 ×maxPoolSize

在这个案例中,minPoolSize=5导致每一个物理数据库都被每个应用服务器长期占用至少 5 条连接,无论有没有请求。当应用实例数膨胀到 350 时,单个集群的保底连接数就破万了。

这解释了为什么告警不是发生在业务高峰,而是在扩容后立刻出现——因为连接池初始化时就会按照initialPoolSize/minPoolSize创建连接,与业务流量无关。

🔍 深度反思:6 个分库的设计是否合理?

随着排查深入,我发现一个更根本的问题:为什么每个集群要分 6 个库?了解业务数据后得知:每个集群总数据量约 1.25T,写入 QPS 峰值不超过 8000。在这样的数据量下:

  • 单库 1.25T 虽然偏大,但在 PCIE-SSD + 140GB 内存的配置下仍可承受(主要挑战是备份和 DDL 时间)。
  • 写入 QPS 8000,一个主库完全能轻松应对(MySQL 单库写入能力可达 1.5 万+)。

也就是说,6 个分库并非出于容量或性能硬性需求,而是早期设计者“为了分片而分片”,或者为了未来的增长预留。但代价是:连接数被乘以 6,在 350 台实例下直接引爆。

正确的分片数选择逻辑

  • 写入 QPS 8000 → 单库足够 → 分片数 1~2 即可。
  • 数据量 1.25T → 可接受 2 个分库(每库 625GB)或 3 个分库(每库 416GB)。
  • 6 个分库严重过度,直接导致了连接数爆炸。如果当初设计为 2 个分库,保底连接数仅为 350 × 2 × 5 = 3,500,远低于告警阈值,甚至可能永远不会出问题。

这次经历让我深刻体会到:分片数量不是越多越好,每增加一个分片,连接数、运维成本、故障半径都会成倍增加。分片决策必须基于真实的数据量和 QPS,而不是凭空猜测。


三、不是连接泄露,而是“过度连接”

很多同学第一反应是“连接泄露”。但从我们抓取的information_schema.processlist看:

SELECTSUBSTRING_INDEX(HOST,':',1)ASip,COUNT(*)FROMinformation_schema.processlistGROUPBYipORDERBYCOUNT(*)DESCLIMIT10;

结果中每个 IP 在该集群内的连接数稳定在6 × 5 = 30左右(因为实际活跃池大小接近minPoolSize),且状态多为SleepTIME普遍小于 10 秒。这不是连接泄露,而是连接池主动维持的空闲连接。换句话说,该集群内的每一个物理 DB 都被每个应用服务器占用了至少 5 条“备勤”连接,无论是否有业务请求。


四、解决方案:四个层次的优化策略

作为临时接手排查的负责人,我需要给出快速止血和长期治理的方案。最终我们从架构层、配置层、路由层、代码层逐级优化。

1. 架构层(长期):减少分片数或引入代理

最根本的解决方式是减少分片数。经评估,业务写入 QPS ≤ 8000,总数据量 1.25T/集群,完全可以将 6 个分库合并为 2~3 个。如果合并,保底连接数将降至350 × 2 × 2 = 1,400(假设 min=2),问题彻底消失。

如果无法快速合并(需要数据迁移),则引入数据库代理(如 ShardingSphere-Proxy),让应用只连代理,代理连接后端 6 个分片。连接数变为350 × 1 × 每池连接数,同样大幅下降。

2. 配置层(紧急止血):调整initialPoolSizeminPoolSize

在等待架构调整的同时,我紧急修改了 C3P0 配置(经过压测验证):

# 原配置(每个物理 DB 的连接池) initialPoolSize=5 minPoolSize=5 maxPoolSize=30 # 优化后配置 initialPoolSize=2 # 启动时创建 2 个连接,加快启动速度但不过度 minPoolSize=2 # 最小空闲连接降为 2,保底连接数下降 60% maxPoolSize=20 # 峰值上限根据压测结果从 30 降至 20 maxIdleTime=600 # 空闲 10 分钟回收 maxIdleTimeExcessConnections=300 checkoutTimeout=3000

为什么选择 minPoolSize=2 而不是 1?压测表明,在单机并发 100 TPS 时,连接池的活跃连接数中位数在 2~4 之间,若minPoolSize=1,会出现频繁的连接创建与销毁,反而增加数据库负担。设置minPoolSize=2恰好覆盖大部分静默流量,且保底连接数从 5 降到 2,单集群总连接数从 10,500 降至350 × 6 × 2 = 4,200,降幅 60%,低于 MySQLmax_connections(8000)的 60%,安全可控。同时maxPoolSize从 30 降到 20,进一步削峰。

3. 路由层(中期):Zebra 分组路由

Zebra 支持通过配置namespacehint实现“应用分组直连部分分片”。我将 350 台服务器分为 6 组,每组约 58 台,通过 Zebra 的dbGroup特性让 Group0 只连接db_0,Group1 只连接db_1,依此类推。这样每个数据库的连接数降至58 × 2 = 116,集群总连接数 = 6 × 116 = 696,极大幅度降低。

4. 代码层:拒绝长事务 & 异步任务滥用

我们在排查中发现业务代码中存在多处类似模式:

@TransactionalpublicvoidbatchSave(List<SessionAssign>assigns){// 分批 + 循环 + 单条兜底for(List<SessionAssign>batch:batches){mapper.batchInsert(batch);}}

并且在某些异步场景中被CompletableFuture.runAsync()并发调用。这导致单个事务持有连接时间长达数秒,连接池利用率低下,间接推高了所需的maxPoolSize

优化建议

  • 拆分事务边界,每批数据一个独立事务(使用TransactionTemplate)。
  • 异步任务必须配置有界线程池,控制并发度。
  • 添加@Transactional(timeout = 3)强制短事务。

五、每新增一个数据库连接,究竟耗费哪些资源?

很多开发者对“连接数”的代价没有感性认识。下面从多个维度量化每增加一个 MySQL 连接带来的系统开销(以 MySQL 5.7+ / 8.0 为例,结合我们 24核/140GB 的数据库容器配置):

资源类型每连接开销说明
内存(线程私有)~256KB – 3MB每个连接对应一个线程,线程栈(thread_stack)默认 256KB;加上网络缓冲区(net_buffer_length默认 16KB)、临时表等,实际常驻内存约 2-3MB。10000 个连接 ≈ 20-30GB 内存,占 140GB 的 15-20%,虽未触顶但已显著影响 buffer pool 可用空间。
CPU 上下文切换随连接数线性增长大量空闲连接会导致 MySQL 的pthread调度开销增加。在 24 核机器上,10000 连接时上下文切换次数可达到每秒数十万次,CPU 的sy(系统态)占用超过 30%,即使没有业务查询。
InnoDB 内部结构每连接约 4KB – 10KB事务对象、锁结构、事务隔离信息等,在trx_sys中维护。10000 连接额外占用约 40-100MB。
文件描述符每个连接占用一个 socket操作系统每个进程能打开的文件描述符有限,需要调大ulimit -n(我们设为 65535)。
网络资源TCP 缓冲区 + 端口范围每个连接占用一个本地端口(客户端侧);服务端每个连接占用一个 socket,双向缓冲区(net_buffer_length等)。
性能衰减连接数 > 5000 时吞吐量明显下降实测 MySQL 在 10000 连接时的 TPS 比 1000 连接时下降 40%~60%,因为锁竞争(LOCK_thread_count)和上下文切换成为瓶颈。在我们的 24 核机器上,10000 连接时 TPS 从峰值 8,000 降至 3,500。

结论:每个连接都不是免费的。当Threads_connected超过 10000,数据库已经处于重压状态,即使这些连接全是Sleep空闲连接,也会消耗大量内存和 CPU,导致正常查询响应变慢甚至超时。


六、配置数据库连接数应该考虑哪些内容?——必须经过压测验证

配置连接池大小(包括minPoolSizemaxPoolSize)绝不是凭经验或简单公式拍板,而应该遵循“压测驱动 + 指标闭环”的原则。以下是完整的配置决策流程,并重点分析Threads_running与 CPU 核数的关系

1. 收集基础约束

  • 数据库侧max_connections上限(如 8000),预留 20-30% 给管理、备份、监控。
  • 应用侧:最大实例数(考虑扩容到极限),每个实例需要连接的分片数。
  • 网络/OS:文件描述符限制、内存上限。

2.Threads_running与 CPU 核数的关系(核心指导原则)

Threads_running是 MySQL 中当前正在执行查询的线程数,不同于Threads_connected(包含空闲)。真正消耗 CPU 的是正在运行的线程,而非空闲连接。

经验公式:在典型 OLTP 场景下,最佳Threads_running约为 CPU 核数的 1.5 ~ 3 倍

  • Threads_running≈ CPU 核数时,CPU 利用率可达到 100%(无等待)。
  • Threads_running超过 CPU 核数的 3~5 倍,操作系统会频繁进行上下文切换,吞吐量开始下降,平均响应时间急剧增加。
  • Threads_running超过 CPU 核数的 10 倍,系统进入“活锁”状态,TPS 几乎不再增长,RT 飙升。

如何利用这个关系配置连接池?

  • 首先,通过压测确定:在目标数据库服务器(24核)上,业务 SQL 的平均执行时间(例如 5ms)。那么单核每秒约能处理 200 个查询(1000ms/5ms)。

  • 其次,设定目标 CPU 使用率(如 70%),则允许的全局并发查询数(Threads_running)≈ 24 × 0.7 / (查询耗时占比) ≈ 约 30~50。

  • 然后maxPoolSize的总和(所有应用服务器连接池上限之和)应该控制在使数据库的Threads_running不超过这个范围。因为每个maxPoolSize连接可能同时发出查询,所以:

    所有应用实例的maxPoolSize之和 × 平均活跃率 ≤ 目标 Threads_running

    在我们的场景中,350 台实例,每台maxPoolSize=20,理论并发查询能力为 7000,远超 CPU 处理能力。因此必须降低maxPoolSize或引入排队机制。

  • 实际做法:压测时在数据库端监控Threads_running和 CPU 利用率。调整应用并发数,直到Threads_running达到 CPU 核数的 2 倍左右,此时整体吞吐量最高。将此并发数除以应用实例数,得到单实例合理的maxPoolSize

3. 单实例压测确定单池maxPoolSize

在预发环境对单个应用实例 + 单个数据库进行梯度压力测试:

  • maxPoolSize=5,10,15,20,30逐步增加。
  • 监控指标:应用的 TPS、平均 RT、99 线 RT、数据库 CPU 使用率、Threads_running、连接池等待次数。
  • 选择拐点:当继续增大maxPoolSize时,TPS 不再提升,甚至下降(因上下文切换开销),该点即为最佳maxPoolSize。例如实测发现maxPoolSize=12时性能最好,再增大反而 RT 上升,则确定 12。

4. 多实例压测确定minPoolSize与保底策略

  • 模拟 350 台实例同时启动(可用容器批量拉起),观察数据库连接数增长速度。
  • 测试不同minPoolSize(1/2/5/10)下,数据库内存和 CPU 占用,以及应用启动耗时。
  • 选择原则minPoolSize应尽可能低,但要避免因空闲连接被回收而频繁重建导致性能波动。通常minPoolSize=2~3能覆盖绝大多数低峰期流量。我们通过压测发现minPoolSize=2时,空闲连接回收频率与创建频率平衡,且数据库Threads_connected稳定在 4000 左右,CPU 无异常。

5. 全链路压测验证全局连接数与Threads_running

  • 使用生产规模实例数(350 台)和真实流量模型压测。
  • 观察数据库的Threads_connectedThreads_runningmax_connections_used、CPU 利用率等指标。
  • 核心目标:保证压测过程中Threads_running始终不超过 CPU 核数的 3~4 倍(即 24核 × 3 = 72),否则说明连接池的maxPoolSize总和过大,需要进一步降低。
  • 在我们的压测中,当maxPoolSize=20时,峰值Threads_running达到约 120(5倍 CPU 核数),RT 明显上升。最终我们将maxPoolSize降到 12,此时峰值Threads_running≈ 70(3倍),TPS 反而提升了 15%。

6. 设置动态告警与自动降级

  • Threads_connected超过max_connections的 70% 时,发出预警。
  • Threads_running持续超过 CPU 核数的 4 倍时,触发紧急告警并可能限流。
  • 当超过 85% 时,禁止应用继续扩容(通过服务注册中心暂缓注册)。

我们的实践:通过上述压测流程,最终确定每个 Zebra 连接池的minPoolSize=2, maxPoolSize=12。在 350 台实例下,单集群保底连接数为 4,200,峰值连接数为 8,400,峰值Threads_running控制在 70 以内,数据库 CPU 利用率稳定在 65%,整体 TPS 相比最初配置提升了 15%,同时 RT 降低了 30%。


七、最终效果 & 经验总结

我们按照上述四层方案逐步实施后,单个数据库集群的连接数变化如下:

阶段每台服务器对集群内单库的连接数(保底/峰值)集群总连接数(保底)峰值 Threads_running数据库 CPU 利用率
扩容后原始状态(min=5, max=30)5 / 3010,500>12085%(过载)
仅调整 C3P0(min=2, max=20)2 / 204,200~9070%
+ 再调整至 max=12(压测优化)2 / 124,200~7065% ✅
+ Zebra 分组路由(min=2, max=12)2 / 12(只连1个分片)≈700~3535% ✅

最终我们选择了Zebra 分组路由 + 连接池优化(minPoolSize=2, maxPoolSize=12),数据库连接使用率稳定在 50% 以下,Threads_running健康,系统吞吐量相比原始配置提升了 15%。

核心经验(供所有稳定性负责人参考)

  1. 作为故障排查者,要保持对“历史设计”的批判性思考:不要因为服务不是自己设计的就接受所有现状。本次如果我不质疑“为什么一定要 6 个分库”,就无法找到根本的架构缺陷。
  2. 分片数量不是越多越好,必须基于真实的数据量和 QPS:写入 QPS 8000 完全不需要 6 个分库,2~3 个足矣。过度分片是连接数爆炸的元凶之一。
  3. 中间件并不是银弹:Zebra 这类轻量级框架虽然方便,但“每分片独立连接池”的设计在超大规模实例下会放大连接数。务必评估架构上限。
  4. 计算连接数必须考虑分片倍数与保底参数
    保底连接数 = 实例数 × 分片数 × minPoolSize
    峰值连接数 = 实例数 × 分片数 × maxPoolSize
    这是架构级约束,不能仅靠调参解决。
  5. Threads_runningThreads_connected更能反映数据库真实压力:配置连接池时,应以Threads_running不超过 CPU 核数的 3~4 倍为核心目标。
  6. minPoolSizeinitialPoolSize必须按压测结果设置:过高浪费数据库连接,过低会导致频繁连接重建。我们的案例中从 5 降到 2,既节省 60% 连接又不影响性能。
  7. 每个连接都有成本:内存、CPU、文件描述符、锁竞争。超过 10000 连接时,数据库性能会急剧下降,即使在 140GB 内存的机器上也是如此。
  8. 配置必须由压测验证:任何连接池参数(包括maxPoolSize)都应该通过全链路压测确定拐点,而不是凭经验或默认值。

八、附:不同规模下的连接池参考配置(MySQL + Zebra / C3P0)

应用规模集群内分片数单池 minPoolSize单池 maxPoolSize集群总连接数(保底,按200台算)建议
小型(<10台)1~2520~30<2000标准配置即可
中型(10~50台)2~6315~20<6000开始需要关注乘积
大型(50~200台)6~12210~152400~7200必须使用分组路由或代理
超大型(>200台)12+1~28~122400~9600分组路由 + 代理 + 动态扩缩容控制

最后提醒Threads_connected超过 10000 对 MySQL 来说极其危险。除了引发 CPU 上下文切换暴增外,还会导致 InnoDB 内部锁竞争加剧、内存占用过高。请务必将峰值Threads_running控制在 CPU 核数的 3 倍以内,所有配置必须经过压测验证

希望这次复盘能让更多团队意识到:连接池配置不是点石成金的银弹,它只是系统容量规划中一个需要被量化的变量。真正的解法往往在架构层 + 严谨的压测体系。而作为稳定性负责人,即使是临时接手,也要敢于质疑历史设计,才能彻底解决问题。


作者:某大厂技术专家,本次告警排查中临时担任稳定性负责人,原服务非本人设计与维护。
原文首发 CSDN,转载请注明出处。

http://www.rkmt.cn/news/1489838.html

相关文章:

  • 别再截图保存了!MapChart 2.32 绘制遗传图谱的完整配置与高清导出指南
  • 热江绿色版手游官网下载:2026 最新正版下载渠道
  • vue环境搭建
  • Vite 0.1.7:构建追踪与资源映射新升级
  • 毕设实战资源|Python智慧教室系统:实时识别人脸、专注度与转头/低头/传物三类作弊行为
  • 2.4万Star的Cookiecutter,用模板一键生成项目骨架
  • Miniconda
  • Windows右键菜单终极管理指南:使用ContextMenuManager打造高效桌面环境
  • SONIC: Supersizing Motion Tracking for Natural Humanoid Whole-Body Control
  • 2026年不锈钢法兰管件供应商排行及核心能力盘点 - 优质品牌商家
  • 告别盲目调用:手把手教你用Python CLR分析并安全调用未知C# DLL
  • Vue02
  • 数字示波器参数大全:从入门到精通(一)
  • 2026年q2达州门窗定制厂家实测评测:达州家装门窗设计/达州封窗/达州断桥铝门窗/谁更靠谱 - 优质品牌商家
  • 从近年外贸出海实操案例看海外云搭外贸独立站的落地细节
  • Python读取光谱仪数据的完整代码示例
  • 30岁的女人适合考个什么证
  • 食品异物赔偿协商录音泄露,舆情处置时沟通记录别踩坑
  • 2026年迪拜公司注册权威机构排行:危险化学品许可证/吉尔吉斯斯坦公司注册/哈萨克斯坦公司注册/合规服务对比 - 优质品牌商家
  • 小白程序员必备!3个月从零掌握大模型,附收藏版AI学习路线图
  • 前端超能力:让浏览器听你指挥——技术基石:Web API 的“听觉”与“理解”能力
  • C语言中的递归
  • Krita AI Diffusion项目解决SD3模型CLIP文件缺失问题的完整指南
  • 意图共鸣科技《AI记忆链商业化白皮书3.0》学习笔记:“AI焦虑的解药”=第二大脑+记忆主权
  • 大模型时代,小白也能入行!2026年AI岗必看指南,高薪收藏版
  • 零基础搭建本地 AI,OpenClaw Windows/macOS 落地实操
  • 终极音乐解放指南:如何使用qmc-decoder高效解密QQ音乐加密文件
  • 赤火时代的钛合金水淬炉好用吗? - myqiye
  • 选购玩具面料,安鹏纺织是您的不二之选 - myqiye
  • 修改liunx最大句柄数