🟣 在线用户与协作规模(核心盘 6 图)

"现在有多少活人、多少房间"——三种"在线"口径别混,按 user_id 去重才是"人数",跨 worker 是近似值。覆盖核心盘 🟣 组 6 张面板(c1–c6)· 后端核心仪表盘 siliang-backend(job=siliang_backend_worker · 刷新 15s · 时间窗 6h)。

WS 会话(对讲机)· SSE 监听(收音机) 上报 在线数骤降 在线人数是怎么数出来的 —— 从页签到去重 Gauge(近似值) 用户浏览器 一个人 · 开 2 个页签 每个页签各开自己的连接 backend ×4 worker(各自数自己的会话) 253-backend-0 WS 会话 · SSE 监听 253-backend-1 WS 会话 · SSE 监听 253-backend-2 WS 会话 · SSE 监听 253-backend-3 WS 会话 · SSE 监听 sum(...) 把 4 个 worker 加总才是大盘 按 user_id 去重求和 同一人多页签只算 1 人 c1 = 在线用户大数字(Gauge) c2 同口径:聊天人数 / 连接数 陷阱① 近似值 同一用户跨 worker 会被算成两人 拿它看趋势,别拿它做精确考勤 陷阱② 口径之外 全能 / 生图 / 视频 SSE 不在数里 要看 HTTP 在途 c18 · LLM 并发 c14 事故现场:发布后在线人数归零或骤降 = 接入层 / WS 出问题,用户全掉线(口径卡原文) 动作:先看 c1 大数字定级,再下钻 c5 六条线找谁先掉,最后转 c20–c22 查 WS "在线"有三种口径,别混(说明卡 c4) ① 画布 WS 在线(对讲机) siliang_ws_online_users · c1 / c5 ② 聊天 SSE 在线(收音机) active_listener_users / listeners · c2 ③ 生成类 SSE(全能 / 生图 / 视频) 不在这两个数里 → c18 在途 · c14 LLM 并发 对讲机 = WebSocket 双向长连接 · 收音机 = SSE 单向推流 · 大数字给"现在",趋势线(c5)给"往哪走"

💡 一句话理解

把 backend 想成一个游乐场:c1 的大数字是"此刻操场上有多少个不同的学生"(按学生证 user_id 去重),c3 是"有多少间教室里还有人"(有活跃 WS 会话的画布房间)。它们都是 Gauge(温度计),直接读数,不累积。

为什么重要?它是整个仪表盘的"体温计之首":产品火不火、发布后用户有没有掉光、故障波及多少人,第一眼看的就是它。但先记住两个口径陷阱:同一人跨 worker 会被算成两人(近似值);全能/生图/视频的 SSE 不在这两个数里。

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

想象你是游乐场的园长,想知道"现在园里有多少人"。门口闸机不记名,只数"刷了几次卡"——张三上午进园一次,同伴帮他又刷了一次,闸机就把他数成两个人。backend 数在线用户也是这样:每个 worker 只看得见自己手里的 WebSocket 会话,它不知道"253-backend-0 上的那条会话"和"253-backend-2 上的那条"是不是同一个人。所以按 user_id 去重之后,跨 worker 的同一个人仍可能被算成两人——这就是口径卡里写的"近似值"三个字的来历。

再看"人数"和"电话线"的区别。聊天室里有两个数:"几个人"(去重用户,一人开 5 个页签只算 1)和"几条电话线"(在途 SSE 连接数,每个页签一条)。WebSocket 是对讲机(画布协作,双向喊话),SSE 是收音机(聊天推流,单向播)。收音机开几台,不代表有几个人在听。健康状态下,连接数 ≈ 人数 × 人均页签数,比例稳定;如果连接数独自一路涨、人数不动,那就是"收音机开了没人关"——监听生成器泄漏,挂掉的 SSE 没人挂断。

最后是"在线"的三种口径(说明卡 c4 的内容):① 画布 WS 在线;② 聊天 SSE 在线;③ 全能/生图/视频的 SSE——不在这两个数里,想看它们得去 HTTP 在途(c18)和 LLM 并发(c14)。数错口径,结论就会南辕北辙:把生成类用户算进"在线人数",会让 c1 看起来"虚低";反过来拿 c1 回答"有多少人在等生图",又永远对不上。

类比里的东西系统里对应的东西
操场上不同的学生数c1 在线用户数:所有 worker 上的 WS 会话按 user_id 去重后求和——"去重"管一人多页签,"求和"管 4 个 worker
一人被闸机刷了两次同一用户跨 worker 各持一条会话被算成两人——所以 c1 是近似值,看趋势别做考勤
开着没人关的收音机挂了没人挂断的 SSE 连接——c2 连接数持续上涨不回落 = 监听生成器泄漏
有人待着的教室数c3 在线协作画布数:有活跃 WS 会话的 workspace 数,衡量协作广度,阈值 100/300
闸机只认本入口每个 worker 只数自己的会话,sum(...) 跨 worker 求和才是大盘;只信 job=siliang_backend_worker
闭园广播(人全走了)c1 归零或骤降 = 接入层 / WS 出问题——先看大数字定级,再下钻趋势线

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

  1. 打开核心盘看 c1 大数字——记下背景色区间:<50 绿 / 50–200 黄 / >200 红,对"现在多少人"心里有个数。
  2. 把时间范围切到 24h 看 c5——应看到 6 条线随白天/黑夜起伏、彼此比例稳定,这就是"一天的业务节奏"。
  3. 对比 c2 的两个数——连接数 ÷ 人数 ≈ 人均页签数,是个稳定的小数字;这个比例突变才值得警觉。
  4. 用自己的账号开两个页签——刷新 c1:数字未必 +2(去重与跨 worker 位置都会影响),体会"近似值"的含义。

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

Gauge(温度计)
在线人数可上可下,是 Gauge 不是 Counter——直接读数,永远不要对它求 rate()。
# 对:直接读此刻的数(Gauge 不配 rate)
sum(siliang_ws_online_users{job="siliang_backend_worker"})
去重 user_id
"人数"= 会话按 user_id 去重:一人开 5 个页签只算 1。但同一人跨 worker 会被算成两人,所以是近似值。
# 近似:跨 worker 的同一人可能被算成 2 人(口径卡原文)
sum(siliang_ws_online_users{job="siliang_backend_worker"})
三种"在线"口径
① 画布 WS 在线 ② 聊天 SSE 在线 ③ 全能/生图/视频 SSE 不在内——第三种要看 HTTP 在途(c18)与 LLM 并发(c14)。
# 生成类流量不在在线数里,要看在途:
sum(siliang_http_requests_in_progress) by (method)
人数 vs 连接数
人数=几个人;连接数=几条线(每页签一条)。健康时 连接数 ≈ 人数 × 人均页签数;c2 阈值 100/400。
# 聊天室:人数与连接数(面板口径照抄)
sum(siliang_chat_resume_active_listener_users)
  / sum(siliang_chat_resume_active_listeners)
job 过滤
只信 job=siliang_backend_worker:共享端口的 server job 随机命中 worker 会双计数,所有面板已排除。
# 不带过滤 = 可能混进双计的 server job
{job="siliang_backend_worker"}   # 唯一可信口径
sum(...) 跨 worker 求和
每个 worker 只报自己的数,sum 把 4 个 worker 加总才是大盘;想看单台用顶部 HOST 变量按机器前缀下钻。
# 大盘 = 4 个 worker 相加(c3 口径)
sum(siliang_ws_workspaces_active{job="siliang_backend_worker"})
outcome 结束原因
电话线怎么挂的也是数据:outcome 是 Counter,配 rate 后按 outcome 拆开,就是 c6 的四条线。
# Counter 配 rate,按 outcome 拆(c6 口径)
sum(rate(siliang_chat_resume_listener_outcome_total[5m])) by (outcome)
大数字 vs 曲线
c1/c2/c3 大数字给"现在",c5 曲线给"往哪走";只看大数字会错过"正在爬升"的堆积。
# 六条趋势线之一:pubsub 订阅任务
sum(siliang_ws_pubsub_tasks_active)   # 独自爬升 = 在堆积

📋 逐面板精讲 6 张图一张不落

🟣 c1 · 在线用户数(协作画布去重)

它是什么:核心盘的第一张大数字:此刻有多少人"坐在画布前"。实现上是把 4 个 worker 手里的 WebSocket 会话按 user_id 去重后求和——"去重"意味着一人多页签只算一人,"求和"意味着它是 4 个 worker 各自报数的合计。它是 Gauge:随业务节奏起伏,不累积。

回答什么问题:产品的实时热度如何?发布/故障后用户是不是掉光了?

✅ 随业务节奏起伏;背景色 <50 绿 / 50–200 黄 / >200 红

🚨 归零或骤降 = 接入层/WS 出问题;注意同一用户跨 worker 会被算成两人,这是近似值。动作:先看 c5 趋势确认是"掉光"还是"正常回落",再转 WS 组(c20–c22)定位。

# 面板 PromQL(照抄主图谱口径)
sum(siliang_ws_online_users{job="siliang_backend_worker"})

联动:主图谱 c1 ↗ · 相关课程:Counter vs Gauge · WebSocket 画布协作

🟣 c2 · 聊天在线(去重用户 / 连接数)

它是什么:聊天室的两个数并排看:"几个人"(去重用户,一人开 5 个页签只算 1)和"几条电话线"(在途 SSE 连接数,每个页签一条)。SSE 是服务器单向推流的收音机,断线可从上次位置续听——四两的 chat_resume(断线续听)名字就是这么来的。

回答什么问题:聊天功能有多少真人在线?连接数和人数比例正常吗?

✅ 连接数 ≈ 人数 × 人均页签数,波动正常;阈值 100/400

🚨 连接数持续上涨不回落 = 监听生成器泄漏(挂了的 SSE 没人挂断)。动作:把窗口拉到 6h 确认"只涨不跌",再去 c6 看挂断原因分布找证据。

# 面板 PromQL(照抄主图谱口径:人数 / 连接数)
sum(siliang_chat_resume_active_listener_users)
  / sum(siliang_chat_resume_active_listeners)

联动:主图谱 c2 ↗ · 相关课程:WebSocket 画布协作 · 后台生成任务(泄漏同源思想)

🟣 c3 · 在线协作画布数

它是什么:有多少个"画布房间"里还有人——有活跃 WS 会话的 workspace 数。人数(c1)衡量"来了多少人",画布数衡量"协作散布在多广":10 个人挤在同一间房,和 10 个人各开各的房,业务意义完全不同。

回答什么问题:协作发生的房间数——衡量真实协作广度。阈值 100/300。

✅ 随业务节奏起伏,与 c1 人数同涨同跌、比例稳定;面板内置 100/300 参考阈值

🚨 房间数与人数的比例突变(房间涨、人不涨)说明协作形态变了,值得结合 c5 六条线一起看;两数齐跌则是真实流量下降

# 面板 PromQL(照抄主图谱口径)
sum(siliang_ws_workspaces_active{job="siliang_backend_worker"})

联动:主图谱 c3 ↗ · 相关课程:WebSocket 画布协作 · job / instance / label

🟣 c4 · 在线用户口径(先读我)

它是什么:在线组的说明书,写清两件事。第一,"在线"有三种,别混:①画布 WS 在线 ②聊天 SSE 在线 ③全能/生图/视频的 SSE——不在这两个数里(要看 HTTP 在途和 LLM 并发)。第二,为什么只信 job=siliang_backend_worker:共享端口的 server job 随机命中 worker、会双计数,已从所有面板排除。

回答什么问题:看数之前先问"这个数是怎么数的、没数谁"——口径不清,后面全白看。

✅ 说明卡没有健康态——它的"健康" = 你看懂口径之后再去看其余五张图

🚨 最大的危险是拿口径②的数回答口径①的问题,或者忘加 job 过滤把双计数的 server job 加进来

# 口径卡的核心:所有面板统一排除 server job
sum(siliang_ws_online_users{job="siliang_backend_worker"})
# 生成类 SSE 不在在线数里:去 c18(HTTP 在途)/ c14(LLM 并发)看

联动:主图谱 c4 ↗ · 相关课程:job / instance / label · HTTP 健康(第三种口径去哪看)

🟣 c5 · 在线用户与协作规模趋势

它是什么:把在线组所有"温度计"画成 6 条时间曲线:去重用户、聊天用户、在线画布、WS 会话总数、pubsub 订阅任务、聊天 SSE 监听。如果说 c1/c2/c3 是照片,这张就是录像——大数字只告诉你"现在多少",曲线才告诉你"往哪走"。

回答什么问题:一天的业务节奏长什么样?增长还是流失?发布前后对比如何?

✅ 曲线平滑起伏、彼此比例稳定

🚨 某条线脱离大部队独自爬升 = 对应资源在堆积(如 pubsub 任务)。动作:顺着那条线找所属模块下钻,先确认堆积源头再谈处理。

# 六条曲线的取数(主图谱口径展开)
sum(siliang_ws_online_users)                    # 去重用户
sum(siliang_ws_workspaces_active)               # 在线画布
sum(siliang_ws_sessions_active)                 # WS 会话总数
sum(siliang_ws_pubsub_tasks_active)             # pubsub 订阅任务
sum(siliang_chat_resume_active_listener_users)  # 聊天用户
sum(siliang_chat_resume_active_listeners)       # 聊天 SSE 监听

联动:主图谱 c5 ↗ · 相关课程:WebSocket 画布协作 · max_over_time 与 deriv

🟣 c6 · 聊天监听结束原因分布

它是什么:每条聊天"电话线"挂断时都会记一个原因(outcome):completed=正常听完;disconnected=用户直接关页面(大多数,正常);lease_expired=worker 租约失效后收敛退出;error=读流出异常。outcome 是 Counter,配 rate 按 outcome 拆开,就是"挂断方式"的实时分布。

回答什么问题:SSE 链路健壮吗?

✅ disconnected + completed 占绝对多数

🚨 error 频繁 = 上下行不稳;lease_expired 高 = 实例在异常退出。动作:error 高先查网络与上下游;lease_expired 高对照发布记录与 c11(跨进程收尸)。

# 面板 PromQL(照抄主图谱口径)
sum(rate(siliang_chat_resume_listener_outcome_total[5m])) by (outcome)

联动:主图谱 c6 ↗ · 相关课程:后台生成任务(租约/收尸机制) · rate():算速度

🏭 生产实战 real world

场景 1 · 发布后的第一眼:用户掉没掉

滚动发布会断开旧连接,在线数抖一抖是正常的;要抓的是"掉下去不回来"。

# 发布窗口前后对比在线人数(Gauge 直接读,不配 rate)
sum(siliang_ws_online_users{job="siliang_backend_worker"})
# 健康形态:发布瞬间小幅回落(旧连接断开重连),1–2 分钟回到原水位
# 危险形态:掉下去不回来 —— 接入层/WS 出问题,接着往下看

# c5 六条线逐条展开,找"谁先掉的"
sum(siliang_ws_online_users)                    # 去重用户
sum(siliang_ws_workspaces_active)               # 在线画布
sum(siliang_ws_sessions_active)                 # WS 会话总数
sum(siliang_chat_resume_active_listeners)       # 聊天 SSE 监听(电话线)
# 判读:全部同跌 = 接入层/网络;只有 WS 线掉 = WS 专属问题(转 c20–c22)

判读收尾:几分钟内回到原水位 = 发布无恙;持续低迷 = 按上面的分支下钻。

场景 2 · 聊天连接数爬升:抓监听器泄漏

收音机开了没人关——挂掉的 SSE 没人挂断,连接数就会一路涨而人数不动。

# 两个数分开看(c2 口径)
sum(siliang_chat_resume_active_listeners)       # 几条线(阈值 100/400)
sum(siliang_chat_resume_active_listener_users)  # 几个人(去重)

# 挂断原因分布,找"该挂没挂"的证据(c6 口径)
sum(rate(siliang_chat_resume_listener_outcome_total[5m])) by (outcome)
# error 频繁   = 读流出异常,上下行不稳
# lease_expired 高 = worker 租约失效被收敛退出(实例问题,转 grp-bg-tasks)
# 临门一脚:窗口拉到 6h,连接线只涨不跌 = 泄漏实锤

判读收尾:人数平稳 + 连接线单调爬升 = 泄漏,立刻报给值班开发。

场景 3 · "明明只有 30 人,为什么数出 38?"

两个数对不上时,先查口径再查 bug——九成是口径问题,不是数据坏了。

# 第 1 步:有没有混进 server job(双计元凶,口径卡 c4)
sum(siliang_ws_online_users)  # 错:不带过滤,混入双计
sum(siliang_ws_online_users{job="siliang_backend_worker"})  # 对:唯一可信口径

# 第 2 步:剩下的差值多半是跨 worker 同人双算(近似值,正常现象)
# 第 3 步:用会话总数交叉验证量级
sum(siliang_ws_sessions_active)   # 未去重口径,应 ≥ 去重人数
# 判读:差值 ≈ 跨 worker 双算量级 = 正常;差值巨大 = 查注册表泄漏(内存盘 m120)

场景 4 · 每日巡检 2 分钟:在线组的两眼

主图谱排查速查表里"每日例行巡检"的第一站就是在线组——便宜、快、信息量大。

# 每日巡检(主图谱 #triage「每日例行巡检(2 分钟版)」口径)
sum(siliang_ws_online_users{job="siliang_backend_worker"})  # c1:和昨天同时段比
sum(siliang_chat_resume_active_listeners)                     # c2:电话线在正常区间?
# 判读:与昨日同时段同量级 = 过关
#       骤降/归零 = 按「场景 1」的路径下钻,先接入层后 WS

场景 5 · 周报配图:用 c5 讲业务节奏

增长还是流失?把时间窗切到 7d,三条核心线就是现成的周报素材。

# 周报配图:7d 窗口下的核心三条线(c5 口径子集)
sum(siliang_ws_online_users)                   # 去重用户 —— 热度
sum(siliang_ws_workspaces_active)              # 在线画布 —— 协作广度(阈值 100/300)
sum(siliang_chat_resume_active_listener_users) # 聊天用户 —— 粘性
# 判读:三条线同步上台阶 = 真增长
#       只有画布涨、用户不涨 = 空房间变多,协作在退潮

⚠️ 常见坑 pitfalls

坑 1 · 对在线人数求 rate — 曲线鬼畜、毫无意义。原因:在线人数是 Gauge(温度计),rate 只能配 Counter(里程表)。正解:Gauge 直接读数;要看"变化速度"用 deriv 或对比两个时刻。
# 错: rate(siliang_ws_online_users[5m])                    # Gauge 求 rate,无意义
# 对: sum(siliang_ws_online_users{job="siliang_backend_worker"})
坑 2 · 把近似值当精确值 — 症状:和产品后台的"注册在线"总差几个人,以为监控坏了。原因:同一用户跨 worker 各持一条会话会被算成两人(口径卡原文)。正解:拿 c1 看趋势与量级,不做精确考勤;用会话总数交叉验证。
# 错: 拿 c1 的大数字和业务后台逐人核对          # → 永远对不齐
# 对: sum(siliang_ws_sessions_active)              # → 会话口径交叉验证
坑 3 · 拿聊天在线当全站在线 — 症状:c2 的数远小于预期,怀疑用户丢了。原因:全能/生图/视频的 SSE 不在这两个数里(口径③)。正解:生成类流量去 c18(HTTP 在途)和 c14(LLM 并发)看。
# 错: 用 c2 回答"全站多少人在线"
# 对: sum(siliang_http_requests_in_progress) by (method)   # → 生成类看在途
坑 4 · 连接数 = 人数 — 症状:一人开 5 个页签被当成 5 个用户,热度虚高。原因:连接是"电话线"(每页签一条),人数是去重后的"几个人"。正解:两个数一起读,连接数 ≈ 人数 × 人均页签数。
# 错: 拿 listeners 当用户数汇报
# 对: users = sum(siliang_chat_resume_active_listener_users)  # → 去重人数
坑 5 · 只看大数字不看曲线 — 症状:c1 显示 45"一切正常",其实 pubsub 任务线已经独自爬了一小时。原因:大数字是照片,没有方向。正解:大数字旁边永远配着趋势图(c5),巡检两张一起看。
# 错: 只盯 c1 当前值
# 对: sum(siliang_ws_pubsub_tasks_active)   # → 六条趋势线一起看(c5)
坑 6 · 忘加 job 过滤 — 症状:自建面板的数比仪表盘大一截。原因:共享端口的 server job 随机命中 worker,会双计数(口径卡 c4)。正解:所有查询一律带 {job="siliang_backend_worker"}。
# 错: sum(siliang_ws_online_users)                             # → 混入双计
# 对: sum(siliang_ws_online_users{job="siliang_backend_worker"})
坑 7 · 把 disconnected 当故障 — 症状:c6 里 disconnected 一枝独大,以为聊天天天炸。原因:disconnected = 用户直接关页面,是大多数、是正常的。正解:健康口径就是 disconnected + completed 占绝对多数;该警惕的是 error 和 lease_expired。
# 错: 看到 disconnected 高就报警
# 对: 只盯 error(上下行不稳)与 lease_expired(实例异常退出)两条线
坑 8 · 把 lease_expired 当聊天 bug — 症状:c6 的 lease_expired 线冒头,去翻聊天代码一无所获。原因:lease_expired = worker 租约失效后收敛退出,问题在实例层不在聊天层。正解:对照发布记录与 c11(跨进程收尸)看是不是有实例异常退出。
# 错: lease_expired 高 → 查 SSE 代码
# 对: sum(rate(siliang_chat_resume_stale_finalized_total[5m]))  # → 收尸计数互证

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

Q1 · c1 的"在线用户数"是怎么数出来的?为什么说它是近似值?

参考答案:把 4 个 worker 手里的 WebSocket 会话按 user_id 去重后求和。近似在两点:每个 worker 只看得见自己的会话,同一用户跨 worker 各持一条会被算成两人;而且去重只能对"报上来的数"做。所以它适合看趋势和量级,不适合做精确考勤。

Q2 · 聊天室的"人数"和"电话线"分别是什么?什么比例形态说明出事了?

参考答案:人数 = 去重用户(一人多页签只算 1);电话线 = 在途 SSE 连接数(每页签一条)。健康时连接数 ≈ 人数 × 人均页签数,比例稳定。连接数持续上涨而人数不动 = 监听生成器泄漏——收音机开了没人关,挂了的 SSE 没人挂断。

Q3 · 老板问"用全能生图的人有多少在线",你能拿 c1 回答吗?

参考答案:不能。口径卡(c4)写明"在线"有三种:画布 WS 在线、聊天 SSE 在线、全能/生图/视频的 SSE——第三种不在这两个数里。要看生成类流量,去 c18(HTTP 在途请求)和 c14(LLM 并发调用数)。拿错口径的数回答问题,结论必然跑偏。

Q4 · 发布后 c1 掉了一半,你的排查顺序是什么?

参考答案:第一步看 c5 六条趋势线,确认是"全部同跌"还是"某条先掉";第二步分支:全部同跌查接入层/网络,只有 WS 线掉转 c20–c22(连接数、连接结果、错误类型);第三步看 c6 的 outcome 分布——error 高是链路不稳,lease_expired 高是实例异常退出,对照发布记录。整个过程先定性(掉没掉)再定位(掉在哪)。

← 上一课:5xx vs 4xx:谁的错 📚 课程目录 下一课:🔴 后台生成任务:内存泄漏核心哨兵 →