🔌 连接池:图书馆借书

建连接贵,所以先建一柜子反复借:checked_out = 借走的书 · capacity = 藏书量 · 使用率 = 借出 ÷ 藏书(80% 黄 / 95% 红)。覆盖 内存盘 m115 / m116 / m117 / m113 · biz 盘 b5 / b6 · worker 池 m129 / m130。

连接池 = 图书馆借书 建一条连接要走 TCP 握手 + 鉴权(贵)→ 先建一柜子反复借;使用率 = 借出 ÷ 藏书 请求一条连接 checked_out +1 checked_out -1 用户请求进来 要用 DB / Redis 连接池(书柜) capacity 藏书量是上限 借到连接干活 复用,不用重新建 还书回池 下次接着借 ✅ 健康路:随借随还,使用率 60% 使用率 = 借出 18 ÷ 藏书 30 = 60% 80% 黄 95% 红 有人还就有人借到,简单查询也秒回 两条警戒线都在远处,告警安静 绝对值卡(m116)看「离天花板还剩几条」 每条线 = 一个 worker × 一个池(per-worker 各一座馆) 🚨 危险路:全借光,排队等还书 使用率 = 借出 80 ÷ 藏书 80 = 100%(async 池) 已突破 95% 红线 → 顶死 100% 第 81 个请求借不到连接,开始排队 排队拉高延迟 → 并发更高 → 排队更狠(雪崩) 💥 事故现场:池打满 → 一片超时与 5xx 慢查询占着书不还,是打满最常见的原因 四两的池子台账(数字以仪表盘口径为准) backend DB:sync 30 / async 80 / checkpointer 30(三张借书证,per-worker 各一座) biz DB:10 + overflow 20 = 30(临时加座)· Redis:shared 上限 50,与 Agent 服务共用 review:独立小池 5+5 · 阈值:80% 黄 / 95% 红 · 使用率卡与绝对值卡成对出现 对应面板:m115/m116(backend DB)· m117/m118(Redis)· b5/b6(biz DB)· m129/m130(worker 池)· m113(client 存活)

💡 一句话理解

去图书馆借书不用自己买书:书(数据库连接)早就建好一柜子,你来借(checked_out 加一),看完还(checked_out 减一),书反复用。为什么这么设计?因为建一条连接很贵——TCP 握手、鉴权几十毫秒——如果每个请求都现建一条、用完就扔,系统会把自己耗死在「买书」上。

台账上最重要的数就一个:使用率 = 借出 ÷ 藏书。80% 是黄色警戒(该留意了),95% 红色(临界),100% = 全借光,后来的请求只能站在柜台前排队等还书——哪怕它只想查一个电话号码。池打满是「接口变慢→雪崩」链条的中继站,所以它同时是铁三角课里说的那个 80% 刹车点。

🧩 费曼拆解 讲给完全没接触过的小白

你是图书馆馆长。书很贵:每进一本新书要走采购、编目、上架一整套流程(对应 TCP 握手、鉴权),如果每个读者来了都现买一本、看完就扔,图书馆早就破产了。所以你一次进够一柜子书,读者来了借,看完还,书被反复借用——这就是连接池。四两的 backend 给 MySQL 办了三张借书证,按用途分了三个柜:sync 30(同步查询)/ async 80(异步)/ checkpointer 30(检查点)。

馆里最要命的时刻是「全借光」。第 81 个读者来了,async 柜 80 本书全在别人手里——他只能等,等到有人还书为止。他可能只是想查个电话号码(一条 1 毫秒的简单查询),也得陪着整个馆等。更糟的是:等的人越多,每个人借书前干等的时间越长(延迟涨),赖在馆里不走的人就越多(并发涨),书还得更慢——这就是铁三角课里那条雪崩链,池子正是它的中继站。

四两的馆还不止一座,各有各的脾气:biz 的柜子平时 10 个位子、高峰临时加座到 30(10 + overflow 20 = 30,overflow 就是折叠椅);Redis 的 shared 柜上限 50,还和 Agent 服务共用——邻居借多了你也受影响;review 服务有个独立小柜 5+5,慢任务一来特别容易被借空。记住:池是 per-worker 的——「sync 30」是每个 worker 各 30 本,4 个 worker 整机是 120 本。

类比里的东西系统里对应的东西
藏书量(柜子容量)capacity:池上限(backend sync 30 / async 80 / checkpointer 30;biz 10+overflow 20=30;Redis shared 50)
借走的书checked_out / in_use:当前借出的连接数(Gauge,直接读)
借书证(按用途分柜)pool 标签:sync / async / checkpointer / shared / broadcaster_pubsub(广播专柜)
临时加座(折叠椅)overflow:biz 池平时 10 条,高峰临时开到 30(10 + 20 = 30)
全借光、排队等还书使用率 100% = 池打满:请求排队,延迟涨 → 并发涨 → 排队更狠(雪崩中继站)
图书馆闭馆检修DB/Redis 不可用:一本书都借不到,全线报错(与锁冲突、依赖故障面板互证)

🛠 动手验证(4 步,在 Grafana 里亲手做一遍)

  1. 看使用率——打开内存盘 🔌 数据库连接池使用率(m115):每条线是一个 worker × 池,看它们平时在 30%~60% 的哪个位置晃,记住「平时的样子」。
  2. 看绝对值——切到借出数与容量上限卡(m116):实线是各 worker 借了几条,水平参考线是藏书量(sync 30 / async 80 / checkpointer 30)——数一数「离天花板还有几条」。
  3. 对 biz 的账——打开 biz 盘 DB 池(b5/b6):确认 biz 的天花板是 30(10 + overflow 20),不是 10;面板按机器 repeat 成对出现。
  4. 看 Redis 的两本借书证——打开 m117:shared 与 broadcaster_pubsub 两个池各自的使用率。广播池打满 = 画布协作消息发不出去,和业务查询打满是两种不同的病。

🧠 必知必会 看懂本课全部面板的地基

checked_out / in_use = 借出数
DB 池叫 checked_out,Redis 池叫 in_use——名字不同,都是「此刻借出去几条」。它是 Gauge,直接读数。
# m116 口径:各 worker 借出数 + 容量参考线
siliang_db_pool_connections{state="checked_out",job="siliang_backend_worker"}
capacity = 藏书量(天花板)
容量也是一个指标(多数时候是常数),画成水平参考线最直观——绝对值卡就是这么干的。
# 各池的天花板:sync 30 / async 80 / checkpointer 30
max by (pool) (siliang_db_pool_capacity{job="siliang_backend_worker"})
使用率 = 借出 ÷ 藏书
核心公式。分子分母都套 max_over_time 抹抓取抖动,并显式 on(instance, pool) 对齐,避免毛刺和错配造成假告警。
# m115 口径:80% 黄 / 95% 红,贴 100% = 池打满
max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])
  / on(instance, pool) max_over_time(siliang_db_pool_capacity[1m])
backend 三池各管一摊
不是「一个大池」,而是按用途分柜:sync 同步查询、async 异步、checkpointer 检查点——打满的柜子不同,病因也不同。
# 按池拆开看谁紧张
max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) by (instance, pool)
# 上限:sync 30 / async 80 / checkpointer 30(per-worker)
biz 的 overflow 是临时加座
biz 池平时 10 条,高峰靠 overflow 临时开到 30——所以它的天花板是 30 而不是 10,别看错参考线。
# b6 口径:biz 借出数 + 上限 30 参考线(10 + overflow 20)
siliang_biz_db_pool_connections{state="checked_out",job="siliang-biz"}
Redis shared 50 与邻居共用
Redis 池不是你独享:shared 池与 Agent 服务共用上限 50,对方用得多也会把你的使用率顶上去——排查时先问「是不是邻居挤占」。
# m117 口径:shared 与 broadcaster_pubsub 各一张借书证
max_over_time(siliang_redis_pool_connections{state="in_use"}[1m])
  / on(instance, pool) max_over_time(siliang_redis_pool_capacity[1m])
broadcaster_pubsub 是广播专柜
画布协作的跨机器广播走专用池——它打满不影响 SQL 查询,影响的是「你画我看不到」的协作消息。
# 广播池紧张时,去 WS 错误面板看 redis_pubsub 是否大于 0
siliang_redis_pool_connections{state="in_use",pool="broadcaster_pubsub"}
review 独立小池 5+5
worker 们(rag/review)有自己的小馆:review 是独立池 5+5,慢任务一来特别容易被借空——小池更要盯。
# m130 口径:worker 池绝对值 + 上限参考线
siliang_worker_db_pool_connections{state="checked_out",job="siliang-review"}
两张卡成对:使用率 + 绝对值
使用率卡看「趋势与告警」(百分比好设阈值),绝对值卡看「离天花板还剩几条」(好估算余量)。两张卡回答两个问题,缺一不可。
# 余量 = 天花板 − 借出(绝对值卡的用法)
max by (pool) (siliang_db_pool_capacity)
  - siliang_db_pool_connections{state="checked_out"}
出站总机与 client 存活
到外部 provider 的 httpx 共享 client 也是池:m119 看每 worker 占几条线;m113 看「总机本身还在不在」——掉 0 = 连接复用被回退,退回每次新建连接,FD 和内存跟着抖。
# m113 口径:1 = 总机存活;有流量时掉 0 即异常
max by (provider) (max_over_time(siliang_shared_http_client_alive[1m]))

🔗 在四两监控里哪里用到 理论落回你的 89 张图

① 数据库连接池使用率(m115)——借出 ÷ 藏书,按 worker × 池拆;80% 黄 / 95% 红,贴 100% = 池打满请求排队:🔌 数据库连接池使用率 ↗

② 数据库连接池借出数与容量上限(m116)——绝对值版:实线借出数 + sync 30 / async 80 / checkpointer 30 参考线,看离天花板多远:🔌 数据库连接池借出数与容量上限 ↗

③ Redis 连接池使用率(m117)——shared(业务共用,上限 50 与 Agent 服务共用)+ broadcaster_pubsub(广播专柜)两张借书证:🔌 Redis 连接池使用率 ↗

④ biz 数据库连接池(b5 / b6)——biz 盘的同款镜像:使用率卡 + 绝对值卡,天花板 10 + overflow 20 = 30,按机器 repeat 成对出现:🟢 biz 数据库连接池使用率 ↗ · 🟢 biz 借出数与上限 ↗

⑤ 共享 HTTP client 存活(m113)——出站「电话总机」还在不在:1=存活;有聊天流量时掉 0 = 连接复用回退,FD 与内存跟着抖:🔌 共享 HTTP client 存活 ↗

🏭 生产实战 real world

场景 1 · 池打满 3 分钟定位:先看谁打满,再找谁不还书

接口成片超时,m115 一片贴 100%。第一步按池拆看是哪个柜子打满(async 打满多半是慢查询占着不还),第二步对齐铁三角:并发与延迟同时刻异动,坐实「排队」。

# 第一步:哪个 worker × 哪个池在贴 100%?
max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])
  / on(instance, pool) max_over_time(siliang_db_pool_capacity[1m])

# 第二步:绝对值——离天花板还差几条(m116 口径)
siliang_db_pool_connections{state="checked_out"} by (instance, pool)

# 第三步:互证雪崩链——同一时刻 P99 是否突起
topk(5, histogram_quantile(0.99,
  sum(rate(siliang_http_request_duration_seconds_bucket[5m])) by (le, handler)))
# 判读:池先满 → 延迟后涨 = 池是根因;反过来 = 延迟把池顶满

判读收尾:找到贴 100% 的那条线对应的 instance + pool,直奔该时段慢查询日志。

场景 2 · 池告警规则:80% 提醒,95% 出手

两个严重度的同一规则:80% 是刹车点(提醒去查),95% 是临界(准备限流/杀慢查询)。都要求持续 5 分钟,避免抓取毛刺造成狼来了。

groups:
- name: siliang-pool
  rules:
  - alert: DbPoolUsageWarning
    expr: |
      max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])
        / on(instance, pool) max_over_time(siliang_db_pool_capacity[1m]) > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "{{ $labels.instance }} 的 {{ $labels.pool }} 池使用率超 80%(刹车点)"

  - alert: DbPoolUsageCritical
    expr: |
      max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])
        / on(instance, pool) max_over_time(siliang_db_pool_capacity[1m]) > 0.95
    for: 5m
    labels:
      severity: critical

判读收尾:阈值与 for 时长以各盘「先读我」口径卡为准,这里是通用模板。

场景 3 · 算余量:离天花板还剩几条

容量巡检不用看百分比,直接算「还剩几本可借」。余量长期小于池的 10%,就该在下次变更里调池或优化慢查询。

# 每个 worker 每个池的剩余可借数
max by (instance, pool) (siliang_db_pool_capacity)
  - max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])

# biz 同款:天花板是 30(10 + overflow 20),别按 10 算
max (siliang_biz_db_pool_capacity)
  - max_over_time(siliang_biz_db_pool_connections{state="checked_out"}[1m])
# 预期:平峰余量大于 50% 高峰余量大于 10%,否则列入调参待办

场景 4 · 广播池打满:协作「失明」而 SQL 正常

用户反馈「我画了他看不到」,但接口都不慢——这是 broadcaster_pubsub 专柜的事,与业务 DB 池无关。联合 WS 错误面板互证。

# 广播专柜使用率(Redis 池的 pubsub 分柜)
max_over_time(siliang_redis_pool_connections{state="in_use",pool="broadcaster_pubsub"}[1m])
  / on(instance, pool) max_over_time(siliang_redis_pool_capacity[1m])

# 互证:WS 错误里 redis_pubsub 大于 0 = 跨实例广播失明(c22 口径)
sum(rate(siliang_ws_errors_total[5m])) by (error_type)
# 判读:广播池贴 100% + redis_pubsub 报错 = 消息发不出去,查 Redis 状态

场景 5 · client 存活掉 0:连接复用被回退

m113 平时是一条 1 的直线。有聊天流量时某 provider 掉到 0,说明共享 client 没了——系统退回「每次现买书」模式,FD 和内存开始抖动。

# 各 provider 的总机存活(1 = 在,0 = 没了)
max by (provider) (max_over_time(siliang_shared_http_client_alive[1m]))

# 互证:出站线路占用是否出现锯齿(复用健康时应平稳,m119 口径)
siliang_http_client_connections{state="active"} by (instance, client)
# 判读:存活掉 0 + 线路数剧烈抖动 = 连接复用回退,按回退事故处理

⚠️ 常见坑 pitfalls

坑 1 · 相除忘了 on(instance, pool) 对齐 — 症状:使用率忽大忽小甚至超 100%。原因:借出数和容量的 label 集合没对齐,Prometheus 默认配对错乱。正解:照抄 m115 口径,显式 on(instance, pool)。
# 错: max_over_time(checked_out[1m]) / max_over_time(capacity[1m])          # → 错配
# 对: ... / on(instance, pool) max_over_time(siliang_db_pool_capacity[1m])   # → 对齐
坑 2 · 忘了池是 per-worker 的 — 症状:容量预算差 4 倍。原因:「sync 30」是每个 worker 各 30 本,不是整机 30 本。正解:容量规划按 worker 数相乘,面板每条线就是一个 worker。
# 错: backend sync 池整机 = 30 条            # → 少算 4 倍
# 对: 30 条/worker × 4 worker = 整机 120 条
坑 3 · 把 biz 池天花板当 10 — 症状:biz 使用率「超 100%」吓死人。原因:漏算了 overflow——天花板是 10 + 20 = 30。正解:看绝对值卡(b6)的参考线,别自己心算。
# 错: 借出 12 ÷ 10 = 120%?              # → 分母错了
# 对: 借出 12 ÷ 30 = 40%(10 + overflow 20 = 30)
坑 4 · 把 Redis 池当自己独享 — 症状:自己没加流量,shared 池使用率却涨了。原因:shared 池与 Agent 服务共用上限 50,邻居借多了你也紧张。正解:先分清涨的是哪个 pool,再判断是不是邻居挤占。
# 错: shared 池涨 = 我们的问题          # → 可能是 Agent 服务在用
# 对: 按 pool 拆开看:shared 涨查双方,broadcaster_pubsub 涨查广播
坑 5 · 池打满只扩池不查慢查询 — 症状:池从 30 调到 60,过阵子又满了。原因:打满是「书借走不还」(慢查询占着连接),不是「书太少」——扩柜子只是推迟。正解:先修延迟(铁三角的根因),池大小是最后才动的旋钮。
# 错: 池满 → 改大池配置 → 两周后再次打满
# 对: 池满 → 找打满时段的慢查询 → 修索引/缩短事务 → 使用率自然回落
坑 6 · 广播池和业务池混为一谈 — 症状:把 broadcaster_pubsub 打满当成「数据库要挂了」全员加班。原因:广播专柜打满影响的是跨实例协作消息(画布同步),SQL 查询毫发无损。正解:按 pool 拆开判断症状:先问「用户感知是什么」。
# 错: Redis 池 100% → 全站告警乱枪打鸟
# 对: broadcaster_pubsub 满 = 协作失明;shared 满 = 业务抖 → 两套动作
坑 7 · 对池 Gauge 求 rate — 症状:「借书速度」算出来忽正忽负没法看。原因:checked_out 是温度计不是里程表,借了还、还了借。正解:直接读数看水位;要抹抖动用 max_over_time。
# 错: rate(siliang_db_pool_connections{state="checked_out"}[5m])  # → 无意义
# 对: max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])

🎓 费曼自测 合上书,能讲出来才算懂

Q1 · 为什么建连接要池化?「用完就扔」不行吗?

参考答案:建一条连接要 TCP 握手加鉴权,几十毫秒起步——比一条简单查询本身还贵。用完就扔意味着每个请求都先交一笔「买书钱」,高峰期大家都在买书而不是看书,系统把自己耗死在握手和鉴权上。池化就是先建一柜子反复借还,让贵的资源被复用。

Q2 · 使用率 100% 时用户体感是什么?你先查什么?

参考答案:体感是「成片变慢和超时」——新请求借不到连接,在池前排队等还书。先查三件事:哪个 worker 的哪个池打满(m115 按池拆);同时刻 P99 是否突起(坐实排队,c19);打满是不是从某条慢查询开始(占着书不还的是它)。修慢查询是根治,扩池只是推迟。

Q3 · biz 的 DB 池天花板是多少?怎么组成的?

参考答案:30。组成是「基础 10 + overflow 临时加座 20」——平时最多借 10 条,高峰靠 overflow 临时开到 30。所以看 b5/b6 两张卡时参考线是 30,别拿 10 当分母算出「120% 使用率」的假警报。

Q4 · Redis shared 池使用率莫名上涨,可能是谁的锅?

参考答案:先记住它不是你独享的——shared 池上限 50,与 Agent 服务共用。所以可能是我方业务涨、可能是 Agent 服务挤占、也可能是 Redis 本身变慢导致连接归还变慢(借书的人不是不还,是还书窗口排队)。按 pool 拆开(区分 shared 和 broadcaster_pubsub)、按 instance 拆开(区分哪台机器),再对时间戳找同时刻异动的邻居。

← 上一课:QPS·并发·延迟铁三角 📚 课程目录 下一课:RSS / VMS / working_set:三种「内存」 →