backend 干每件活都要向三家「图书馆」借资源:MySQL 三格书架、Redis 两张借书证、外网电话总机。台账=6 张图:借出数、容量上限、使用率、总机存活。覆盖 m113 · m115 · m116 · m117 · m118 · m119 · 后端内存仪表盘「🔌 连接池」组(内存盘 6 图)。
连接池就是「图书馆借书制」:连接(书)提前建好一柜子,用的时候借(checked_out),用完就还;柜子总量就是藏书量(capacity),借光了后来的人排队等还书。backend 干每件活要借三家:跟 MySQL 借数据库连接(三格书架 sync 30 / async 80 / checkpointer 30)、跟 Redis 借两张借书证(shared 业务池上限 50,与 Agent 服务共用;broadcaster_pubsub 画布广播专用池)、对外打 HTTP 走 httpx 出站总机(active=占线几路,alive=总机还在不在)。
这 6 张图就是三家图书馆的借阅台账:每张卡回答同一件事——「书还够不够借」。书够,一切快;书借光(池打满),所有人排队,接口延迟跟着雪崩。
为什么要有池?因为「建一条连接」很贵:跟 MySQL 建连接要 TCP 握手加认证,跟外网建 HTTPS 还要再加 TLS 协商,几十毫秒起步——就像每次借书都现盖一座图书馆。所以程序启动时就建好一柜子连接反复借还。四两 backend 的书柜分三格:sync 30(同步查询)、async 80(异步查询并发高,同一瞬间要借的书最多,所以格子最大)、checkpointer 30(检查点写库走独立书架,免得跟业务抢书)。
Redis 那边是两张借书证:shared 是业务共用池(上限 50,而且这本证还跟 Agent 服务合用——对方用得多,你的使用率也会被抬 high);broadcaster_pubsub 是画布广播专线(你在 A 机器画一笔,要靠它把消息发到 B 机器的同伴屏幕上),它打满 = 协作消息发不出去。最后是出站总机 httpx:到各家 provider 的电话线也是池化的,active 数占线,alive 说明总机本身还在不在——总机没了就退回「每打一通电话现拉一条线」,FD 和内存跟着抖。
台账有「两张卡」的分工(口径卡原话):使用率卡(借出÷藏书)看趋势、配告警,80% 黄、95% 红;绝对值卡(实线借出数 + 藏书量参考线)看离天花板还有几条。两张卡一起看:使用率告诉你「快不行了」,绝对值告诉你「还差几本到底」。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 借出的书 | checked_out(DB 池)/ in_use(Redis 池)/ active(httpx 占线)——正在干活的连接数 |
| 藏书量 / 借书证额度 | capacity:backend 三格书架 sync 30 / async 80 / checkpointer 30;Redis shared 上限 50 |
| 两张借书证 | Redis 的 shared(业务共用,与 Agent 服务共用)与 broadcaster_pubsub(画布广播专线) |
| 出站电话总机 | httpx 共享 client:active=占线线路数,alive=总机在不在(1=存活,0=没了) |
| 借阅告警线 | 使用率 80% 黄 / 95% 红;贴 100% = 池打满,请求排队(通常伴随某个慢查询) |
| 每家分店各一本账 | 池是 per-worker 的:每个 worker 独立一套池,图上一条线 = 一个 worker 的一格书架 |
shared 与 broadcaster_pubsub 两条线:广播池贴 100% 就是协作事故,别跟业务池混着看。# 使用率的分子分母(DB 池) siliang_db_pool_connections{state="checked_out"} # 借出几条 siliang_db_pool_capacity # 藏书量(天花板)
# 三格书架在图上是三条参考线(绝对值卡) max by (pool) (siliang_db_pool_capacity) # → sync=30, async=80, checkpointer=30
# 以 sync 格 30 条为例(per-worker) 30 × 4 worker = 120 # 整机实际能同时借出这么多,规划时要乘
# 使用率卡(趋势/告警) max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) / on(instance,pool) max_over_time(siliang_db_pool_capacity[1m])
# 两张借书证按 pool 标签分开看 siliang_redis_pool_connections{state="in_use"} # by (instance, pool) 拆线 # → pool="shared" 业务池 · pool="broadcaster_pubsub" 广播池
# 雪崩链条(铁三角) # 延迟翻倍 → 并发翻倍 → 使用率贴 100% → 排队更狠 → 雪崩 # 所以 80% 是刹车点,不是参考建议
# 每 worker 占用的出站线路(主图谱口径) siliang_http_client_connections{state="active"} by (instance, client) # → 某个 worker 独自飙高 = 它的任务在狂打外部请求
# 总机存活(1=在) max by (provider) (max_over_time(siliang_shared_http_client_alive[1m])) # → 有聊天流量时出现 0 = 复用被回退,退回每次新建连接
它是什么:借出书 ÷ 藏书量的百分比,按 worker × 池(sync / async / checkpointer)拆成多条线。这是 DB 图书馆的「饱和度」读数——告警配在它身上,因为百分比不受容量影响,三格书架可以放同一张图里比。
回答什么问题:哪个 worker 的哪格书架快借空了?打满的趋势是从什么时候开始的(常能对上一次发布或一条慢查询)?
✅ 健康长啥样:各线围绕低位波动(远低于 80%),偶有业务高峰冲一下就回落;三条线形态大体同步(业务忙大家都忙)。
🚨 危险长啥样:80% 黄、95% 红;某条线贴 100% = 池打满,请求开始排队(通常伴随某个慢查询)。动作:先找占着连接不还的慢查询 / 顺手看锁冲突面板,而不是无脑调大池。
# 主图谱口径:借出数 ÷ 容量,on(instance,pool) 保证分子分母对上同一条书架 max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) / on(instance,pool) max_over_time(siliang_db_pool_capacity[1m])
联动:主图谱 · DB 池使用率 ↗ · 相关课程:连接池:图书馆借书 · QPS·并发·延迟铁三角 · 数据库锁冲突
它是什么:上一张的绝对值版:实线是各 worker 此刻借了几条连接,水平参考线是该池的藏书量(sync 30 / async 80 / checkpointer 30)。它不告诉你「百分比危不危险」,但直观告诉你「离天花板还有几条」——调容量、估算余量时看这张。
回答什么问题:async 池现在借出 62 条还是 79 条?离 80 的天花板还剩多少缓冲?容量要不要调?
✅ 健康长啥样:实线在参考线下方从容起伏,高峰期也不贴线;三格书架的实线各自独立波动。
🚨 危险长啥样:实线贴平参考线(比如 async 顶在 80 不下来)= 该格书架被借空;配合使用率卡互证后动作:找慢查询、看锁冲突,而不是先动容量。
# 主图谱口径:实线=借出数,参考线=该池藏书量(sync 30 / async 80 / checkpointer 30) siliang_db_pool_connections{state="checked_out"} + max by (pool) (siliang_db_pool_capacity)
联动:主图谱 · DB 池借出数与上限 ↗ · 相关课程:连接池:图书馆借书 · biz 盘的镜像双卡 b5/b6
它是什么:Redis 图书馆的两张借书证各自的使用率:shared(业务共用池,上限 50,还与 Agent 服务共用)和 broadcaster_pubsub(画布广播专用池)。Redis 承载着 pubsub 跨实例广播、任务租约等要命的东西,所以它的池子要单独盯。
回答什么问题:Redis 是不是成了瓶颈?尤其:广播池打满了没有——它打满,画布协作消息就发不出去(你在 A 机器画,B 机器看不见)。
✅ 健康长啥样:两条线都远离 100%;shared 因与 Agent 服务共用会有「别人的波动」,形态偶发抬升后回落属正常。
🚨 危险长啥样:broadcaster_pubsub 贴 100% = 协作消息发不出,去核心盘 WS 错误面板看 redis_pubsub 是否 > 0(广播失明)互证;shared 持续高位但本服务没加流量 = 怀疑 Agent 侧挤占,别急着重启 Redis。
# 主图谱口径:Redis 池使用率(in_use ÷ capacity,按 instance × pool 拆) max_over_time(siliang_redis_pool_connections{state="in_use"}[1m]) / on(instance,pool) max_over_time(siliang_redis_pool_capacity[1m])
联动:主图谱 · Redis 池使用率 ↗ · 相关课程:WebSocket 画布协作 · biz 盘 b7/b8(同一台上限 50)
它是什么:Redis 池的绝对值版——各 worker 在 shared / broadcaster_pubsub 两张证上各借出几条,外加水平上限参考线。和 m116 一样,它是「数格子」的卡:看具体还剩几条额度。
回答什么问题:广播池现在借出几条、离上限还差几条?shared 池的高是均匀分布在各 worker,还是某一个 worker 独自借了一堆?
✅ 健康长啥样:实线远低于上限参考线;各 worker 借出数大体均匀(大家干的活差不多)。
🚨 危险长啥样:某条实线顶平参考线 = 该 worker 该池借空;广播池顶平立刻查协作链路(与 m117、WS 错误面板三角互证),业务池顶平先分清是自家流量还是 Agent 侧挤占。
# 主图谱口径:借出数(实线)+ 上限(参考线) siliang_redis_pool_connections{state="in_use"} + max by (pool) (siliang_redis_pool_capacity)
联动:主图谱 · Redis 池借出数与上限 ↗ · 相关课程:连接池:图书馆借书 · WS 画布协作(广播链路)
它是什么:出站电话总机(httpx 共享 client)上,每个 worker 正在占用的线路数。四两要跟 LLM、COS 等一堆外网说话,这些出站连接也是池化复用的——这张图就是总机的「占线指示灯」。
回答什么问题:哪个 worker 在狂打外部请求?(按 worker 展开,不均衡一眼可见。)下游是不是变慢了?(大家都占着线不放 = 外呼迟迟不挂。)
✅ 健康长啥样:各 worker 的线随业务节奏同起同落;外呼高峰占线多、过后回落。
🚨 危险长啥样:某个 worker 独自飙高 = 它身上的任务在狂打外部请求(对照后台任务/LLM 调用找来源);全员持续高位 = 下游变慢占着线路,去看 LLM 延迟与错误率面板互证。
# 主图谱口径:出站占线数,按 worker × client 拆 siliang_http_client_connections{state="active"} by (instance, client)
联动:主图谱 · HTTP client 借出连接 ↗ · 相关课程:LLM 健康门诊 · 铁三角(占线=并发)
它是什么:到各家 provider 的「电话总机」本身还在不在:1=在,0=没了。它是个防回退哨兵——「共享 client 连接复用」是省内存省延迟的关键设计,曾经有人把它改坏过(退回每次新建连接),这张图就是防止这种回退悄悄上线的警报器。
回答什么问题:连接复用的设计还在不在?发布之后总机有没有被改没了?
✅ 健康长啥样:有聊天流量的时段,每条 provider 线都稳稳贴在 1;没流量的低谷期波动无意义,别误读。
🚨 危险长啥样:有聊天流量时某 provider 掉 0 = 连接复用被回退——退回每次新建连接,FD 和内存跟着抖。动作:对照最近发布回查代码,同时看线程/FD 面板旁证。
# 主图谱口径:总机存活(抹抖动后按 provider 取最大)
max by (provider) (max_over_time(siliang_shared_http_client_alive[1m]))
联动:主图谱 · 共享 HTTP client 存活 ↗ · 相关课程:线程/FD 四件套(抖动旁证) · 防护与缓存(下游响应防线)
症状:好几个不相关的接口 P99 同时抬头。常规做法是挨个翻接口日志——错,先看 DB 池:所有请求共用书架,书架借空是「集体变慢」最常见的公共原因。
# 第 1 步:看哪个 worker 的哪格书架在贴 100%(先定位再开药) max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) / on(instance,pool) max_over_time(siliang_db_pool_capacity[1m]) # → 每条线 = 一个 worker 的一格书架;贴 1.0 的就是打满点 # 第 2 步:打满通常是「占着书不还」的慢查询/锁,顺手对一眼锁冲突哨兵 sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) # → > 0:去锁冲突率面板按 operation, code 定位(1205=等太久,1213=死锁)
判读:使用率回落且锁冲突为 0,是流量高峰的正常挤压;使用率钉死 100% 不动,去日志找那条占着 80 条 async 连接的慢查询。
症状:用户反馈画布协作不同步。协作消息跨机器全靠 Redis 的 broadcaster_pubsub 广播池——它打满,消息就发不出去。
# 广播池使用率(pool 取值以图例为准:shared / broadcaster_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{pool="broadcaster_pubsub"}[1m]) # 互证:核心盘 WS 错误类型里 redis_pubsub > 0 = 广播失明 sum(rate(siliang_ws_errors_total[5m])) by (error_type)
判读:池贴 100% 且 redis_pubsub 错误出现 = 实锤;广播池是专线,被业务池挤到的可能性低,优先查广播 subscriber 是不是堆积了。
池打满到排队只是几秒的事,等人工发现就晚了。把通用阈值固化成两级告警(阈值为口径卡通用口径,for 时长按业务调整)。
# 示例告警规则(Prometheus rule YAML,阈值=主图谱通用口径) groups: - name: db-pool rules: - alert: DbPoolUsageHigh expr: max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) / on(instance,pool) max_over_time(siliang_db_pool_capacity[1m]) > 0.80 labels: {severity: warning} # 黄:开始找慢查询 annotations: {summary: "DB 池使用率 > 80%({{ $labels.instance }} / {{ $labels.pool }})"} - alert: DbPoolSaturated 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 labels: {severity: critical} # 红:贴 100% 就开始排队,P99 跟着起飞 annotations: {summary: "DB 池使用率 > 95%,即将打满排队"}
背景:有人优化代码时把共享 client 换成了「每次请求 new 一个」——功能全对,性能悄悄劣化。m113 就是抓这种回退的哨兵。
# 有聊天流量的时段,每个 provider 的总机都应该是 1 max by (provider) (max_over_time(siliang_shared_http_client_alive[1m])) # → 出现 0:连接复用被回退,退回每次新建连接(TCP 握手+TLS) # 旁证:FD 与内存抖动会同步出现(对照内存盘线程/FD 面板) max_over_time(siliang_process_open_file_descriptors[1m]) max_over_time(siliang_process_resident_memory_bytes[1m])
判读:alive 恒 1 且 FD 平稳 = 复用健在;alive 掉 0 且 FD 锯齿变粗 = 实锤回退,回查该时段的发布。
shared 池是和 Agent 服务合用的借书证——对方用得多,你的使用率也会高。看到 Redis 池高就重启 Redis,十有八九是打错靶子。
# shared 池使用率(与 Agent 服务共用,上限 50) max_over_time(siliang_redis_pool_connections{state="in_use",pool="shared"}[1m]) / on(instance,pool) max_over_time(siliang_redis_pool_capacity{pool="shared"}[1m]) # → 高但本服务没加流量:先对 Agent 侧用量与发布,别急着重启 Redis # 对照:biz 仪表盘 b7/b8 是同一口径(上限 50,max_connections) max_over_time(siliang_biz_redis_pool_connections{state="in_use"}[1m])
判读:两边同时抬高 = 共用方挤占或 Redis 本身变慢(借出去还得慢);只有一边高 = 查那边的流量来源。
checkpointer(检查点)走的是独立书架(容量 30),和业务查询互不抢书——所以它的使用率波动节奏和 sync / async 不同步属正常,巡检时单独过一眼。
# 单独看 checkpointer 格的使用率(业务高峰它不跟涨是正常的) max_over_time(siliang_db_pool_connections{state="checked_out",pool="checkpointer"}[1m]) / on(instance,pool) max_over_time(siliang_db_pool_capacity{pool="checkpointer"}[1m]) # → 天花板 30:贴 100% = 检查点写库排队,和业务池打满是两种病 # 三格书架一屏总览(巡检收尾) max by (pool) (max_over_time(siliang_db_pool_connections{state="checked_out"}[1m])) / on(pool) max by (pool) (max_over_time(siliang_db_pool_capacity[1m]))
判读:sync / async 随业务同步起伏、checkpointer 独立波动 = 三格书架各司其职;三格一起贴 100% = 数据库本身变慢(借了还不上),去查 DB 侧。
# 错: siliang_db_pool_connections{state="checked_out"} > 70 # 写死绝对值,容量一改就废 # 对: max_over_time(…{state="checked_out"}[1m]) / on(instance,pool) …capacity[1m] > 0.80
# 错: "sync 30,所以整机并发上限 30 个查询" # 对: 30 × 4 worker = 120 # 一条线只代表一个 worker
# 错: systemctl restart redis # 打错靶子,还把广播/租约一起抖掉 # 对: by (pool) 分线 → 对照本服务与 Agent 侧流量 → 再定性
# 错: 只盯 5xx 和延迟,alive 面板常年不看 # 对: 发布后看一眼 alive=1 + FD 平稳,两个旁证一起确认复用健在
# 错: async 80 → 160 # 慢查询还在,书多了也一样被占光 # 对: 先定位慢查询/锁 → 修复 → 观察使用率回落 → 再评估容量
# 错: checked_out > 70 # 对 checkpointer(30) 永假,对 async(80) 太晚 # 对: 使用率 > 0.80 # 三格书架通用
# 错: connections / capacity # 标签没对齐,结果错配 # 对: max_over_time(connections[1m]) / on(instance,pool) max_over_time(capacity[1m])
参考答案:每次干数据库的活都要先「借一本书」(连接)。书提前买了一柜子反复借还,很便宜;但柜子就那么大(sync 30 / async 80 / checkpointer 30)。有人的书借着不还(慢查询),柜子借空后,后面所有人只能排队等还书——哪怕他只想查一行数据。于是所有接口一起变慢,这就是「池打满连坐」。
参考答案:分工不同。使用率卡=借出÷藏书,是百分比,回答「危不危险」,三格书架能放一张图比,告警配在它身上(80% 黄 / 95% 红);绝对值卡=实线借出数+藏书量参考线,回答「离天花板还差几条」,调容量、估余量时看它。一张管趋势告警,一张管具体余量。
参考答案:广播池是画布协作的专线,打满=协作消息发不出去——用户看到「你画我看不见」的跨机器协作失明。顺序:先看 m117/m118 确认广播池贴 100%;再去核心盘 WS 错误面板看 redis_pubsub 是否 > 0 互证;最后查广播 subscriber 堆积的来源(对照 WS 消息收发 QPS)。
参考答案:alive=0 说明「连接复用」这个设计被回退了——总机没了,每次外呼都要现拉一条线(TCP 握手+TLS,几十毫秒起步),FD 和内存跟着抖。它平时不报错、用户无感,是慢性劣化,所以专门设了这张哨兵图:有聊天流量时出现 0,就对照发布回查代码。