尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

连接数过高为何拖慢数据库——微服务集群的连接池参数实验、资源竞争与并发预算实战

连接数过高为何拖慢数据库——微服务集群的连接池参数实验、资源竞争与并发预算实战 文章目录每日一句正能量1. 背景与问题1.1 连接和并发不是同一概念1.2 为什么应用线程等连接不一定是坏事2. 环境与数据2.1 为什么要设置application_name2.2 基础监控SQL2.3 为什么state特别重要3. 复现过程3.1 微服务是如何不知不觉制造1950个连接的3.2 P5050个连接3.3 P100100个连接3.4 P200200个连接3.5 P500500个连接3.6 P10001000个连接3.7 为什么CPU 99%并不等于“CPU利用得很好”3.8 为什么SQL执行计划没有变性能却变差4. 方案实施4.1 第一原则先做数据库级连接预算4.2 max_connections不是应用预算本身4.3 第二原则从实例数反推单实例池4.4 一个实用估算公式4.5 第三原则控制active而不是只控制total4.6 第四原则在线和批处理分池4.7 第五原则连接池等待应成为削峰机制4.8 第六原则minimumIdle不能盲目等于maximumPoolSize4.9 第七原则扩容必须重新计算连接预算4.10 第八原则慢SQL会占住连接更久4.11 第九原则work_mem会被并发放大4.12 第十原则不要用max_connections掩盖连接池问题5. 结果对比5.1 原始状态5.2 E1每实例池减半5.3 E2增加数据库并发门禁5.4 E3批处理独立池5.5 E4回收大量idle连接5.6 E5只提高max_connections5.7 汇总5.8 为什么active峰值120比200更快5.9 连接池利用率也要同步看5.10 用等待事件解释为什么数据库慢6. 风险与复盘6.1 风险一池调得太小6.2 风险二只调maxPoolSize不看实例数6.3 风险三弹性扩容绕过数据库预算6.4 风险四连接池太大掩盖慢SQL6.5 风险五idle in transaction6.6 风险六超时设置互相打架6.7 风险七连接池启动风暴6.8 风险八连接泄漏6.9 风险九DBA连接被应用吃光6.10 风险十连接多导致内存预算失控推荐治理模型一个简单预算例子推荐验收顺序回退方案最终结论附录最低验收门禁每日一句正能量“与独处相安与万事言和。”与自己和解让内心成为平静的港湾。与世界和解接纳生活的不完美与无常。主题连接管理 / 微服务集群 / 连接池重点max_connections、sys_stat_activity、active/idle、连接预算、上下文切换、内存放大、等待事件、连接池大小、P95/P99适用场景KingbaseES 上的 Java 微服务、Spring Boot/HikariCP、容器化集群、弹性扩容、多服务共享数据库、批处理与在线业务混合负载。1. 背景与问题微服务架构上线以后数据库经常会遇到一种很反直觉的现象应用连接池开大以后 数据库没有更快 反而更慢例如单个服务实例maximumPoolSize20看起来并不大。但集群里可能有订单服务 40个实例 会员服务 30个实例 营销服务 20个实例 报表服务 10个实例如果每个实例都允许 20~30 个数据库连接那么汇总以后很容易变成几百 甚至上千个连接开发侧常见判断是连接池不够 → 线程在等连接 → 把池调大但数据库侧真正的问题不是能不能接受这些TCP连接而是当大量连接同时进入 active 状态它们会共同竞争有限的 CPU、内存、Buffer、IO、WAL、锁和存储队列超过系统资源拐点后连接越多排队和调度成本越高。KingbaseES 官方连接参数文档说明max_connections决定数据库允许的最大并发连接数量官方运维手册建议监控当前连接数并把连接数超过max_connections的 80% 作为值得告警的资源耗尽信号之一。这说明max_connections更像一个容量上限/保护边界而不是性能越大越好的旋钮1.1 连接和并发不是同一概念数据库里500个连接不一定等于500个正在执行SQL可能400 idle 80 active 20 idle in transaction所以必须区分Total Connections Active Connections Idle Connections Idle in Transaction真正对 CPU、IO 和锁形成强竞争的通常是active连接。但 idle 也不是完全免费因为每个数据库后端仍有进程/会话状态 基础内存 连接槽位 系统资源如果几千个微服务连接长期常驻也会消耗数据库容量。1.2 为什么应用线程等连接不一定是坏事一个典型误区连接池出现等待 池太小实际上数据库已经达到最佳并发时让一部分请求在应用连接池里排队往往比全部放进数据库内部排队更健康。应用层等待通常成本更低 更容易超时 更容易削峰数据库内部过载则可能引发CPU打满 上下文切换 锁等待放大 IO队列 WAL压力 全业务尾延迟上涨所以连接池本质上不仅是连接复用器也是一个并发闸门2. 环境与数据测试环境示例数据库 KingbaseES CPU 32 Core 内存 128GB max_connections 1000 业务 20个微服务实例参与本轮压测 SQL 典型OLTP点查 更新 单SQL正常耗时 2~8ms连接池实验P50 总连接50 P100 总连接100 P200 总连接200 P500 总连接500 P1000 总连接1000固定请求模型 SQL 索引 数据分布 客户端QPS 数据库参数只修改允许进入数据库的并发连接2.1 为什么要设置application_name微服务数据库连接一定要能识别来源。sys_stat_activity官方字段包含application_name client_addr state query wait_event_type wait_event如果所有服务application_name都为空出了事故只能看到500个连接但不知道谁占了400个因此建议order-service member-service report-service分别设置清晰的应用名。2.2 基础监控SQLSHOWmax_connections;SHOWsuperuser_reserved_connections;查看连接状态SELECTstate,COUNT(*)FROMsys_stat_activityWHEREbackend_typeclient backendGROUPBYstate;按服务拆SELECTapplication_name,client_addr,state,COUNT(*)FROMsys_stat_activityWHEREbackend_typeclient backendGROUPBYapplication_name,client_addr,stateORDERBYCOUNT(*)DESC;KingbaseES 官方维护文档也直接推荐使用SHOWmax_connections;SELECT*FROMsys_stat_activity;查看数据库连接容量和会话状态。2.3 为什么state特别重要官方sys_stat_activity文档定义active idle idle in transaction idle in transaction (aborted)其中active代表正在执行查询。idle代表等待客户端的新命令。idle in transaction则代表事务还开着 但当前没有SQL执行这最后一种尤其危险。上一篇已经讨论过它可能同时拖住锁 VACUUM 连接槽位所以连接池治理必须和事务治理一起做。3. 复现过程3.1 微服务是如何不知不觉制造1950个连接的假设订单 40实例 × 20 800 会员 30实例 × 15 450 营销 20实例 × 20 400 报表 10实例 × 30 300理论最大1950每个团队看自己15~30都觉得不大。问题在于数据库看到的是总和更麻烦的是 Kubernetes/HPA 扩容。假设订单服务40实例 →80实例数据库连接需求瞬间800 →1600应用扩容本来为了扛流量结果数据库却被连接池同步放大。这就是微服务数据库最典型的Pool Multiplication问题。3.2 P5050个连接示例Active Peak: 48 CPU: 48% TPS: 18k P95: 38ms P99: 65ms数据库还有大量余量。这时池可能确实偏小。3.3 P100100个连接Active: 96 CPU: 66% TPS: 31k P95: 54ms P99: 92ms吞吐大幅提升。尾延迟仍稳定。这可能是甜点候选区3.4 P200200个连接Active: 188 CPU: 82% TPS: 36k P95: 96ms P99: 210msTPS 从31k →36k继续增加。但P99已经翻倍说明并发开始接近资源边界。3.5 P500500个连接Active Peak: 420 CPU: 97% TPS: 34k P95: 480ms P99: 1.3s关键现象出现连接翻倍 TPS反而下降原因不是连接失败。而是数据库已经进入过载区3.6 P10001000个连接Active: 760 CPU: 99% TPS: 26k P95: 1.9s P99: 4.8s此时更多连接只是在制造更多同时竞争资源的执行单元数据库需要频繁在大量后端之间调度。上下文切换、缓存局部性、锁竞争、IO队列和内存压力都会恶化。3.7 为什么CPU 99%并不等于“CPU利用得很好”高负载系统最容易犯这个错误。CPU99%可能是有效计算也可能大量消耗在调度 上下文切换 锁竞争 cache miss 后台压力所以 OS 侧必须同时观察run queue context switches iowait load average 内存 swap不能只看CPU利用率3.8 为什么SQL执行计划没有变性能却变差这类事故很典型100连接 Index Scan Execution 5ms 500连接 还是Index Scan P99却1秒原因EXPLAIN描述单条SQL如何执行但系统性能还取决于有多少条SQL同时执行相同计划100个并发和500个并发并不是同一个系统状态。因此本文虽然要求突出执行计划但必须强调连接数过高属于“执行计划不变也会恶化”的典型资源竞争问题计划要作为基线证据但不能替代并发监控。4. 方案实施4.1 第一原则先做数据库级连接预算不是每个应用自己决定我需要100而是 DBA/架构先给数据库定义可用应用连接预算例如max_connections 600预留超级用户/系统连接 20 DBA/运维 20 定时任务 20 应急空间 40应用总预算约500这个 500 才是所有微服务共同分享的上限。4.2 max_connections不是应用预算本身KingbaseES 官方参数还提供superuser_reserved_connections专门给超级用户保留连接槽。所以绝对不能max_connections600 然后应用池总和也配置600否则高峰时DBA都可能进不去生产预算必须留Emergency Headroom4.3 第二原则从实例数反推单实例池假设应用预算500一共50个业务实例最简单平均10连接/实例但真实系统不能平均分。例如订单 高优先级 15 会员 10 营销 5 报表 独立限制连接预算必须反映业务重要度 SQL平均耗时 峰值并发 批处理特征4.4 一个实用估算公式可以从目标并发 ≈ QPS × 数据库平均服务时间估算起点。例如QPS 5000 平均DB占用 5ms理论并发5000 × 0.005 25加上波动 P95 事务 安全系数可能需要40~60个有效连接而不是500这个公式不是最终答案。但它能帮助团队摆脱“线程有1000所以连接池也要1000”的错误直觉。4.5 第三原则控制active而不是只控制total假设总连接300其中idle200 active100数据库可能运行很好。另一套总连接200 active190反而可能更危险。因此生产必须监控Total Active Idle Idle in Transaction四个指标。真正的吞吐拐点通常和Active Concurrency关系更强。4.6 第四原则在线和批处理分池最危险报表批处理 和 在线订单共用同一个无限连接池。夜间批任务瞬间拿200连接在线请求就开始锁等待 CPU争抢 P99上涨更合理OLTP池 Batch池 Report池分别限制数据库并发。例如在线 100 批处理 20 报表 10批处理慢一点可以不能拖垮在线核心接口。4.7 第五原则连接池等待应成为削峰机制当数据库安全active120应用突然来500个并发请求应该允许380个在线程/连接池队列等待而不是放500个SQL同时进数据库连接池等待时间需要有connectionTimeout避免无限排队。如果拿不到连接快速失败/降级/排队比数据库全局雪崩更好。4.8 第六原则minimumIdle不能盲目等于maximumPoolSize很多连接池minimumIdlemaxPoolSize会导致每个微服务实例启动以后立即建立满池连接100个实例 × 20直接2000个常驻连接即使当前 QPS 很低。对于大规模微服务集群要评估低minIdle 按需增长 idle timeout降低常驻连接浪费。4.9 第七原则扩容必须重新计算连接预算Kubernetes HPA20 pods →40 pods如果每个maxPool20数据库理论连接需求400 →800所以发布平台应该有实例上限 × pool size门禁。否则应用自动扩容本身可能成为数据库事故触发器4.10 第八原则慢SQL会占住连接更久连接池大小不能独立于 SQL 性能。假设100连接 SQL平均5ms容量很好。如果某次发布SQL平均500ms同样100连接很快全部busy上游看到connection pool exhausted但根因其实是慢SQL所以连接池告警触发后要先看active SQL wait_event query duration而不是第一时间池加倍KingbaseES 官方性能文档也强调sys_stat_activity可以查看数据库有多少连接、连接状态和等待事件并指出长时间执行的查询或事务可能拖累整个系统。4.11 第九原则work_mem会被并发放大假设某类 Sort/Hashwork_mem64MB注意它通常不是一个数据库实例只用一次64MB而可能在多个连接 多个执行节点上使用。如果500 active sessions都跑需要较多工作内存的查询。潜在内存压力可能很大。所以连接数和work_mem必须联合容量规划。不要单独调。4.12 第十原则不要用max_connections掩盖连接池问题典型事故500不够 →1000 →2000每次扩完短期不报“too many connections”但数据库P99越来越差最终还可能OS内存压力 上下文切换爆炸正确顺序应该看服务来源 看active比例 看慢SQL 看等待事件 调应用pool 限制并发 最后才评估max_connections是否真的容量不足5. 结果对比以下为方法演示数据并非生产实测。5.1 原始状态总连接 900 Active 420 TPS 30k CPU 97% P99 2.6s应用团队认为连接池不够但数据库已经明显过载。5.2 E1每实例池减半例如maximumPoolSize 20 → 10集群总连接 470 Active 210结果TPS 35k P99 780ms连接少了。吞吐反而提高。这就是最有说服力的一组实验。5.3 E2增加数据库并发门禁进一步让进入DB执行的active峰值控制在约120结果总连接 430 Active 120 TPS 37k P99 240ms说明最佳吞吐点不是最多并发而是资源不饱和情况下的有效并发5.4 E3批处理独立池报表/ETL最多20活跃连接在线独立100左右结果白天核心P99 160ms批任务完成稍慢。但生产SLA稳定整体收益更高。5.5 E4回收大量idle连接设置合理minimumIdle idleTimeout maxLifetime之后总连接下降Active基本不变。P99变化不大这是正常的。因为 idle 回收主要改善连接槽 基础内存 后台进程数量 运维可控性而不是直接提高一条查询的执行速度。5.6 E5只提高max_connections原先1000继续提高。连接数进一步增加。结果TPS不升 P99继续上升因为真正瓶颈CPU/IO/锁/调度没有改变。5.7 汇总连接/方案Active峰值CPUTPSP99504848%18k65ms1009666%31k92ms20018882%36k210ms50042097%34k1.3s100076099%26k4.8s治理后120约70%37k240ms从曲线可以看出100→200 吞吐仍提高 200→500 开始进入过载 500→1000 连接更多 TPS反而下降5.8 为什么active峰值120比200更快因为 200 active 时CPU已经82%再加锁 IO WAL 后台任务系统开始出现排队。120 左右可能刚好让CPU保持高利用 又不会进入严重争抢这就是并发甜点。5.9 连接池利用率也要同步看应用侧至少暴露Active Idle Pending/Waiting Acquire Time Timeout Count如果数据库只有active 50而应用有大量线程在等确实可能池太小如果数据库active 400 CPU 99%应用还在等绝不能简单加池这两种“连接池等待”含义完全不同。5.10 用等待事件解释为什么数据库慢sys_stat_activitywait_event_type wait_event可以帮助区分CPU竞争 锁等待 IO WAL 客户端等不同瓶颈。连接数只是症状入口最终还需要找到高并发在争抢什么6. 风险与复盘6.1 风险一池调得太小如果安全并发150却把数据库全局限制成20CPU只有20%应用大量等待。这也是错误。治理目标不是连接越少越好而是找到资源甜点6.2 风险二只调maxPoolSize不看实例数单实例10看起来很小。如果200个实例仍然是2000连接所以配置项必须记录成pool size × max instance count6.3 风险三弹性扩容绕过数据库预算HPA必须有数据库容量约束否则前端越扩数据库越容易崩6.4 风险四连接池太大掩盖慢SQL慢SQL上线池耗尽如果先20→100只会让更多慢SQL同时进数据库。CPU和IO更差。应先确认为什么连接借出时间变长6.5 风险五idle in transaction池里的连接如果业务忘了提交状态会是idle in transaction这种连接不只占池。还可能持锁 拖VACUUM是高优先级治理对象。6.6 风险六超时设置互相打架需要一起设计HTTP timeout connection acquire timeout statement timeout lock timeout transaction timeout如果HTTP 1s 连接获取 5s请求早就断了。线程还在等数据库连接。最终制造幽灵负载6.7 风险七连接池启动风暴大规模滚动发布100个Pod同时启动每个minimumIdle20数据库会突然收到2000个建连请求即使平时业务流量并不高。所以发布平台最好分批启动连接池也要避免满池预热6.8 风险八连接泄漏代码没有及时close/return connection池逐渐耗尽。这时症状也是等待连接不能误判为池太小应用要有leak detection和连接借用时长监控。6.9 风险九DBA连接被应用吃光KingbaseES 的superuser_reserved_connections就是为了保留一部分超级用户槽位。但生产还应该额外留运维安全空间不要让业务长期顶在max_connections 99%官方运维资料给出的 80% 告警参考非常实用。6.10 风险十连接多导致内存预算失控基础连接内存只是第一部分。真正危险每个active连接还可能运行Sort/Hash需要work_mem 并行 临时内存所以最大连接数和 SQL 内存参数必须联动。不能连接×10 work_mem仍保持原值然后期待内存安全。推荐治理模型建立DB连接总预算 ↓ 业务分类预算 ↓ 服务预算 ↓ 实例最大数 ↓ 单实例pool size反向配置。不要每个团队各自定pool 最后数据库承担总和一个简单预算例子max_connections: 600 超级用户/系统预留: 20 DBA和应急: 30 批处理: 30 应用可用: 520订单200会员100营销60报表40剩余120作为扩容和突发headroom然后再根据每类服务最大Pod数算单实例池。推荐验收顺序1. SHOW max_connections 2. 统计total/active/idle/idle in transaction 3. 按application_name拆来源 4. 建立各服务实例数 × pool size清单 5. 50/100/200/500并发压测 6. 记录CPU、上下文切换、内存、等待事件 7. 找TPS拐点 8. 把数据库并发限制在拐点附近 9. 在线/批处理分池 10. 验证P95/P99和故障余量回退方案如果连接池调小以后出现应用等待连接过多 吞吐下降 Acquire Timeout不要立即直接翻倍先判断数据库CPU是否还有余量 active是否低于甜点 SQL是否变慢确认确实池偏小后5→8→10小步恢复。如果数据库已经CPU/IO/锁接近上限则应限流 优化SQL 拆批 扩容数据库资源而不是放大连接。最终结论连接池大小的本质不是应用能同时拿多少连接而是允许多少工作同时进入数据库竞争有限资源过小资源没吃满 应用排队适中吞吐最大 尾延迟稳定过大CPU调度 上下文切换 内存 IO 锁 WAL一起竞争。最终表现是连接越多 TPS反而越低 P99越来越高如果只记住一句话max_connections 决定“数据库最多允许多少连接进来”连接池决定“应用最多同时把多少工作压给数据库”性能最优点通常远早于 max_connections 上限。微服务集群真正成熟的连接治理应该做到数据库有总预算 服务有配额 实例扩容能重新计算 在线和批处理隔离 active并发有上限 idle事务可治理 连接等待可观测这比单纯把maximumPoolSize从20改成100更能真正提高系统吞吐和稳定性。附录最低验收门禁[ ] max_connections已记录 [ ] superuser_reserved_connections已记录 [ ] 总连接/active/idle分别监控 [ ] idle in transaction单独告警 [ ] application_name可识别服务 [ ] 每服务实例数×pool size已登记 [ ] HPA最大实例连接预算已核算 [ ] 50/100/200/500并发曲线已测试 [ ] CPU和上下文切换已记录 [ ] 等待事件已记录 [ ] 内存余量已验证 [ ] 在线和批处理池已隔离 [ ] Acquire Time/P95/P99已监控 [ ] 连接数低于预算门禁 [ ] DBA/应急连接空间已预留 [ ] 回退pool配置已保存转载自https://blog.csdn.net/u014727709/article/details/164031324欢迎 点赞✍评论⭐收藏欢迎指正
返回列表