Files
Nexus/docs/design/specs/2026-06-12-server-watch-slots-design.md
2026-07-08 22:31:31 +08:00

17 KiB
Raw Permalink Blame History

设计 — 服务器实时监测槽(4 台自选 · 宝塔式)

日期2026-06-12
状态:待实施
关联讨论:非全舰队;SSH/Redis 探针;8h 自动下架;独立历史页

背景与目标

运维需要在 服务器页手动选定的少数子机(最多 4 台)做 近实时 资源监视(CPU、内存、磁盘、负载、网络/磁盘 IO、进程 Top),体验接近 宝塔面板监控:数字与曲线 约 5 秒刷新,而非现有 10 分钟历史粒度。

不在范围:全舰队 5s 探针;改造现有 Schedules(推送/脚本)页;无 SSH 且无 Agent 的机器无法探针(需明确失败态)。

已确认产品决策

决定
同时监测上限 4 台(固定 4 槽)
选机方式 服务器列表 每行 「+」 加入监测;槽位区也可点「+」选机
范围 仅 Pin 的子机 高频采样;未 Pin 零额外 SSH/探针
生命周期 添加后 8 小时 自动下架;用户可手动删除;重复添加同一台 重置 8h
实时感 目标 5s 刷新(探针 + WebSocket 推送)
数据源 有 Agent 且 Redis 新鲜 → 读 Redis;否则 SSH psutil 脚本
历史 Pin 期间 每次探针均落库(成功/失败皆有记录);独立页面 查趋势 + 探针记录7 天 purge
隔离 按管理员 4 槽(独立表 + admin_id
探针记录 强制:每 5s 一轮必有 DB 行;失败写 probe_status + error禁止只打日志不落库
指标范围 每轮 5s 全量刷新CPU、内存、磁盘使用率、负载、磁盘读写网络入口/出口(带宽)、进程 Top10s);见下节

指标清单(每轮探针必采 · 与宝塔对齐)

原则Pin 期间 所有下列指标同一 5s 周期采集、落库、推 WS、刷新 UI;不得把磁盘/网络放到「二期」或降频。

类别 字段 展示(槽位卡) 说明
CPU cpu_pct 圆环 + 曲线 %
内存 mem_pct 圆环 + 曲线 %
磁盘空间 disk_pct 圆环 + 曲线 根分区 / 使用率 %(与 Agent/SSH 一致)
负载 load_1/5/15 数字 load average
磁盘 IO disk_read_bytes, disk_write_bytes 读/写速率 累计字节;API/前端用相邻两点差算 MB/s
网络出入口 net_bytes_recv, net_bytes_sent 下行↓ / 上行↑ 速率 recv=入口/下载,sent=出口/上传;差算 Mbps 或 MB/s
进程 processes_json Top5 表 10s 刷新内容,仍每 5s 写探针行

速率计算(中央统一,避免前端各算各的):

rate_sent = (sent[t] - sent[t-1]) / (ts[t] - ts[t-1])   → 出口带宽
rate_recv = (recv[t] - recv[t-1]) / (ts[t] - ts[t-1])   → 入口带宽
disk_read_rate / disk_write_rate 同理

首点无上一采样时速率显示 WS 推送 payload 含 已算好的 net_up_bps, net_down_bps, disk_read_bps, disk_write_bps 供卡片直接用。

数据源

路径 CPU/MEM/DISK% 网络 cumulative 磁盘 IO cumulative
RedisAgent 心跳) system_info 已有 ⚠ 心跳 默认无 → Pin 机走 SSH 补采 Agent 小改带上 net_io/disk_io(推荐 M1SSH 脚本一次拿全
SSH 探针 psutil 一次 JSON psutil.net_io_counters() psutil.disk_io_counters()

Pin 且 Redis 仅含 cpu/mem/disk% 时:同轮 SSH 补采 IO/网络,合并为一行 watch_probe_recordssource=redis+ssh 或主来源记 ssh)。

UI(槽位卡 · 宝塔式)

CPU 78%  MEM 62%  DISK 41%
↑ 12.3 MB/s  ↓ 8.6 MB/s     ← 网络出入口(实时)
读 2.1 MB/s  写 0.4 MB/s    ← 磁盘 IO(可选一行,M1 即显示)
[15min 折线:CPU / MEM / DISK / 下行带宽 可切换图例]

方案对比

方案 优点 缺点
A. 仅复用 server_metric_samples10min 无新表 无法宝塔式实时
B. 全舰队 Agent 5s 心跳 真实时 2000+ 台不可接受
C. 自选 4 台 + 5s 探针 + WS(选定) 负载可控;SSH 覆盖无 Agent;体验接近宝塔 需新表、worker、WS 通道
D. 前端 5s 轮询 HTTP 实现简单 延迟与连接数差于 WS;仍要后台探针

选定 C:后台 watch_probe 仅对 Pin 列表采样;Redis 滑动窗口 + MySQL 持久化;WebSocket 推送到浏览器。

信息架构

服务器页布局

StatCardsRow(现有)
WatchSlotRow(新增,4 格)
  空槽:虚线卡片「+ 添加监测」
  有槽:主机名、CPU/MEM/**DISK** 三环、15min 滚动折线(**CPU+内存+磁盘** 可切换)、网络/磁盘 IO 速率、剩余时间、删除
服务器列表(现有)
  操作列新增:mdi-plus 图标按钮「加入实时监测」
  已在监测:显示已占用槽位或 disabled + tooltip「已在监测 · 槽 2」

列表「+」交互

  1. 点击 + → 若未满 4 槽:直接占用 第一个空槽(或弹出选槽 14默认最小空槽号)。
  2. 若已满 4 槽:对话框「监测已满,请选择要替换的槽位」→ 选槽 → 替换。
  3. 已在监测的 server+ 变为 mdi-check 或隐藏tooltip 提示槽位与剩余时间。
  4. 无 SSH 凭据且无 Agent+ 可点但 POST 返回 422snackbar 说明原因(不静默失败)。

独立历史页

  • 路由:#/watch-metrics(侧栏「监测历史」或从 WatchSlotRow「查看历史 →」进入)
  • Tab A — 资源趋势:选 14 台、时间范围 1h/6h/24h/7d;曲线含 CPU / 内存 / 磁盘使用率 / 网络↑↓ / 磁盘读写的速率(由相邻探针差分计算);离线 shading;导出 CSV
  • Tab B — 探针记录:按服务器/时间/状态筛选的 只读表格(见下文「探针记录」);支持导出 CSV
  • 与 Dashboard 舰队均值页区分:本页 单机/自选多机 + Pin 期间细粒度 + 全量探针日志

探针记录(强制)

原则:监测槽的价值不仅在实时曲线,还在 可回溯的探针执行记录——SSH 超时、凭据失败、Redis 过期等必须可查,不得仅写应用日志或静默丢弃。

记什么

每一轮 watch_probe 对每台 Pin 中的 server(去重后)必须 INSERT 一行,无论成功或失败:

字段语义 成功 失败示例
probe_status ok ssh_timeout / ssh_auth / no_cred / redis_stale / offline / parse_error
指标列 有值 NULL(禁止用 0 冒充)
error NULL 人类可读摘要(≤255 字)
duration_ms 探针耗时 同上
source redis / ssh 尝试的数据源

与会话关联

每次用户点列表 + 加入监测,创建一条 watch_pin_sessions8h 监测会话);探针记录通过 session_id 归属到「哪一段 Pin 期间」。

Pin 到期或手动删除 → 更新 ended_at + end_reasonexpired / manual / replaced);已写入的探针记录不删,保留至 purge 策略。

UI 在哪看

  1. 监测历史页 · 探针记录 Tab — 主入口,可跨会话、跨机器查
  2. 监测槽卡片 · 「记录」链接 — 跳转并预填当前 server + 当前 session 时间范围
  3. 服务器列表展开详情(可选 M2 — 若该台正在 Pin,展示最近 20 条探针摘要

运维用途

  • 排查「曲线断档」:按 probe_status=ssh_timeout 过滤
  • 对比 Redis vs SSH:看 source
  • 审计:谁在何时 Pin 了哪台(watch_pin_sessions + audit log

数据模型

watch_pin_sessions

watch_pin_sessions (
  id            INT PK AUTO_INCREMENT,
  admin_id      INT NOT NULL,
  server_id     INT NOT NULL,
  slot_index    TINYINT NOT NULL,
  started_at    DATETIME NOT NULL,
  ended_at      DATETIME NULL,
  end_reason    VARCHAR(16) NULL,   -- expired | manual | replaced
  INDEX (admin_id, started_at),
  INDEX (server_id, started_at)
)

watch_pins 当前活跃 Pin 可 FK 指向 session_id;到期删 pin 行,session 保留

watch_pins

watch_pins (
  id            INT PK AUTO_INCREMENT,
  admin_id      INT NOT NULL FK admins,
  session_id    INT NOT NULL FK watch_pin_sessions,
  slot_index    TINYINT NOT NULL,          -- 0..3
  server_id     INT NOT NULL FK servers,
  pinned_at     DATETIME NOT NULL,         -- UTC naive
  expires_at    DATETIME NOT NULL,         -- pinned_at + 8h
  UNIQUE (admin_id, slot_index),
  UNIQUE (admin_id, server_id),
  INDEX (expires_at)
)

watch_probe_records

表名实现可用 watch_probe_records;与早期草案 watch_metric_samples 合并为 同一张表(指标 + 探针状态一体),避免双写。

watch_probe_records (
  id            BIGINT PK AUTO_INCREMENT,
  session_id    INT NOT NULL FK watch_pin_sessions,
  server_id     INT NOT NULL,
  admin_id      INT NOT NULL,              -- Pin 发起人(查询隔离)
  recorded_at   DATETIME NOT NULL,
  source        VARCHAR(16) NOT NULL,      -- redis | ssh
  probe_status  VARCHAR(24) NOT NULL,      -- ok | ssh_timeout | ...
  duration_ms   INT NOT NULL,
  is_online     BOOLEAN NOT NULL,
  cpu_pct       SMALLINT NULL,
  mem_pct       SMALLINT NULL,
  disk_pct      SMALLINT NULL,             -- 根分区 / 使用率 %
  disk_mount    VARCHAR(255) NULL DEFAULT '/',  -- 采样分区(M1 固定 /
  load_1        FLOAT NULL,
  load_5        FLOAT NULL,
  load_15       FLOAT NULL,
  net_bytes_sent   BIGINT NULL,            -- 累计:网络出口(上传)
  net_bytes_recv   BIGINT NULL,            -- 累计:网络入口(下载)
  disk_read_bytes  BIGINT NULL,
  disk_write_bytes BIGINT NULL,
  net_up_bps       BIGINT NULL,            -- 出口带宽 bytes/s(写入时算好)
  net_down_bps     BIGINT NULL,            -- 入口带宽 bytes/s
  disk_read_bps    BIGINT NULL,
  disk_write_bps   BIGINT NULL,
  processes_json   JSON NULL,              -- top 510s 更新内容
  error         VARCHAR(255) NULL,
  INDEX (session_id, recorded_at),
  INDEX (server_id, recorded_at),
  INDEX (admin_id, recorded_at),
  INDEX (probe_status, recorded_at)
)

写入纪律

  • watch_probe 每 5s 每台 Pin 机至少 1 INSERT;进程 Top 可每 2 轮更新 processes_json(仍写行,仅进程字段可空)。
  • 探针异常:probe_status != ok 时指标列全 NULL + error 必填。
  • 保留 7 天watch_records_purge 每日 batch DELETE。

估算1 台 Pin 8h ≈ 5760 行(5s);4 台 ≈ 2.3 万行/8h,7 天可接受。

Redis(实时滑动窗口)

  • Keywatch:live:{server_id}5s 一点,保留 30 分钟;每点 JSON 含完整指标
{
  "ts": "2026-06-12T14:05:05Z",
  "cpu_pct": 12,
  "mem_pct": 45,
  "disk_pct": 61,
  "load_1": 0.8,
  "net_bytes_sent": 102400,
  "net_bytes_recv": 204800,
  "net_up_bps": 1310720,
  "net_down_bps": 917504,
  "disk_read_bytes": 4096,
  "disk_write_bytes": 8192,
  "disk_read_bps": 2202009,
  "disk_write_bps": 419430,
  "probe_status": "ok",
  "source": "ssh"
}
  • Keywatch:proc:{server_id} — 进程 JSONTTL 60s10s 更新
  • Pub/Subnexus:watch — payload 与上同结构 + server_id, admin_ids[]

后台任务

任务 间隔 职责
watch_probe 5s 读 Pin 并集;Redis 或 SSH每轮每台必 INSERT watch_probe_records;写 Redis 窗口;Pub/Sub WS
watch_pin_expiry 60s 过期 pin → 更新 session.ended_at;删 pin 行
watch_records_purge 每日 DELETE watch_probe_records / 旧 session 元数据 > 7d

Primary worker only(与 heartbeat_flush 一致)。

SSH 探针脚本(单次 JSON,指标一次带回)

remote_probe 基础上扩展,一次 exec 输出:

{
  "cpu_usage": 12.5,
  "mem_usage": 45.2,
  "disk_usage": 61.0,
  "disk_mount": "/",
  "load_1": 0.82,
  "load_5": 0.91,
  "load_15": 1.05,
  "net_bytes_sent": 123456789,
  "net_bytes_recv": 987654321,
  "disk_read_bytes": 1234567890,
  "disk_write_bytes": 9876543210,
  "top_processes": [{"name":"nginx","pid":1234,"cpu_pct":2.1,"mem_pct":1.2}]
}

超时 8s;失败仍 INSERT禁止只采 CPU/内存而跳过磁盘与网络出入口。

API

方法 路径 说明
GET /api/watch/pins 当前管理员 4 槽 + 最新实时指标 + 剩余秒数 + session_id
POST /api/watch/pins { "server_id": 1, "slot_index"?: 0 };创建 session;满则 409
PUT /api/watch/pins/{slot} 替换槽内 server_id(旧 session end_reason=replaced
DELETE /api/watch/pins/{slot} 手动移除(session end_reason=manual
GET /api/watch/live?server_ids=1,2 读 Redis 窗口(兜底轮询)
GET /api/watch/metrics?server_ids=&hours=24 watch_probe_records 聚合曲线(仅 probe_status=ok
GET /api/watch/probe-records 探针记录分页query: server_id, session_id, hours, probe_status, page
GET /api/watch/sessions?hours=168 Pin 会话列表(起止时间、机器、结束原因、探针成功/失败计数)

GET /api/watch/probe-records 响应示例

{
  "items": [
    {
      "id": 9912,
      "recorded_at": "2026-06-12 14:05:05",
      "server_id": 42,
      "server_name": "web-01",
      "source": "ssh",
      "probe_status": "ssh_timeout",
      "duration_ms": 8001,
      "error": "SSH exec timed out after 8s",
      "cpu_pct": null,
      "disk_pct": null,
      "net_up_bps": null,
      "net_down_bps": null
    }
  ],
  "total": 5760,
  "page": 1,
  "per_page": 50
}

鉴权:Depends(get_current_admin);仅可查 本人 admin_id 的 session/记录;CUD Pin 写 audit logwatch_pin_add/remove/replace)。

WebSocket

  • 端点:复用 /ws/alerts 扩展 type: watch_metrics /ws/watch(推荐 新 channel,避免告警拥塞)
  • 订阅:连接后带 JWT;服务端维护 admin_id → socket;仅推送 该 admin 当前 Pin 的 server_id 更新
  • 前端:Servers 页 mount 时 connectunmount disconnect;收到包更新 WatchSlotRow 与 ECharts append

前端组件

组件/页 职责
WatchSlotRow.vue StatCardsRow 下 4 卡;gauge + 15min 折线;倒计时
WatchSlotCard.vue 单槽 UI:三环 + ↑出口 ↓入口 + 磁盘读写速率
useWatchPins.ts Pin CRUD、WS、倒计时
WatchMetricsPage.vue 历史趋势 + 探针记录 Tabv-data-table-server
WatchProbeRecordsTable.vue 状态、source、CPU/MEM/DISK网络↑↓、耗时、error
ServersPage.vue 操作列 mdi-plus;接 POST pin

图表ECharts(与 FleetTrendEChart 同栈);实时 appendData + 窗口上限 360 点。

安全与性能

  • 硬上限:每 admin 4 pin;探针 server 去重(多 admin pin 同一台 → 探针 1 次WS 分 admin 推
  • SSH:仅 Pin 集合;连接池复用;并发 ≤4(可配置)
  • 无静默吞错:SSH 失败、无凭据、超时 → 卡槽红态 + 探针记录行 + 最后 error 文案
  • 进程 JSON:命令行截断 80 字符,不落盘敏感 env
  • 估算负载100 admin × 4 pin 极端 = 400 台(理论);实际运维 个位数 admin × 4 → 探针 QPS ≈ 0.8/s 可接受

与现有能力关系

现有 关系
Agent 60s 心跳 + Redis Pin 且 Redis 新鲜 → 优先,减 SSH
server_metric_samples 10min 保留;与 watch 探针记录 独立;长历史兜底
列表展开 ServerMetricSparklines 保留;与监测槽互补
Schedules 不扩展 schedule_type

验收标准

  1. 服务器列表每行有 +,点击后 5s 内 CPU/内存/磁盘% 与 网络出入口速率 连续更新。
  2. 满 4 槽再点 + 出现替换对话框;手动删除后槽变空。
  3. 8h 到期自动消失(误差 ≤ 2min);再次 + 同一台重置 8h。
  4. 未 Pin 的子机:抓包/日志 额外 SSH 探针。
  5. #/watch-metrics 探针记录 Tab 可见每次 5s 探针(含失败);按 session 筛选与 Pin 时段一致。
  6. SSH 失败:卡槽红态 + DB 有 probe_status=ssh_*显示 0% 冒充正常。
  7. 模拟 SSH 超时单测:断言 仍有 INSERTcpu_pct IS NULL
  8. 探针记录与趋势页均含 disk_pct、net_up_bps、net_down_bps 列/序列;相邻 5s 点带宽差分正确。
  9. local_verify + 单测(含速率差分、全指标 SSH mock+ type-check。

实施分期

阶段 内容
M1 表 + 探针 全指标(含磁盘% + 网络出入口 + 磁盘 IO 速率) + 5s WS + 4 槽 UI + 列表 +
M2 进程 Top5 抽屉 + 探针记录 Tab + 历史趋势多序列(含带宽曲线)
M3 CSV 导出 + Agent 可选补 net_io(减 SSH+ 告警联动

回滚

  • 删除路由与 UI;停 watch_probe;表可保留;Redis key TTL 自愈。