17 KiB
设计 — 服务器实时监测槽(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、内存、磁盘使用率、负载、磁盘读写、网络入口/出口(带宽)、进程 Top(10s);见下节 |
指标清单(每轮探针必采 · 与宝塔对齐)
原则: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 |
|---|---|---|---|
| Redis(Agent 心跳) | ✅ system_info 已有 |
⚠ 心跳 默认无 → Pin 机走 SSH 补采 或 Agent 小改带上 net_io/disk_io(推荐 M1:SSH 脚本一次拿全) |
|
| SSH 探针 | ✅ psutil 一次 JSON | ✅ psutil.net_io_counters() |
✅ psutil.disk_io_counters() |
Pin 且 Redis 仅含 cpu/mem/disk% 时:同轮 SSH 补采 IO/网络,合并为一行 watch_probe_records(source=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_samples(10min) |
无新表 | 无法宝塔式实时 |
| 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」
列表「+」交互
- 点击 + → 若未满 4 槽:直接占用 第一个空槽(或弹出选槽 1~4,默认最小空槽号)。
- 若已满 4 槽:对话框「监测已满,请选择要替换的槽位」→ 选槽 → 替换。
- 已在监测的 server:+ 变为 mdi-check 或隐藏,tooltip 提示槽位与剩余时间。
- 无 SSH 凭据且无 Agent:+ 可点但 POST 返回 422,snackbar 说明原因(不静默失败)。
独立历史页
- 路由:
#/watch-metrics(侧栏「监测历史」或从 WatchSlotRow「查看历史 →」进入) - Tab A — 资源趋势:选 1~4 台、时间范围 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_sessions(8h 监测会话);探针记录通过 session_id 归属到「哪一段 Pin 期间」。
Pin 到期或手动删除 → 更新 ended_at + end_reason(expired / manual / replaced);已写入的探针记录不删,保留至 purge 策略。
UI 在哪看
- 监测历史页 · 探针记录 Tab — 主入口,可跨会话、跨机器查
- 监测槽卡片 · 「记录」链接 — 跳转并预填当前 server + 当前 session 时间范围
- 服务器列表展开详情(可选 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 5;10s 更新内容
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(实时滑动窗口)
- Key:
watch: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"
}
- Key:
watch:proc:{server_id}— 进程 JSON,TTL 60s,10s 更新 - Pub/Sub:
nexus: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 log(watch_pin_add/remove/replace)。
WebSocket
- 端点:复用
/ws/alerts扩展type: watch_metrics或 新/ws/watch(推荐 新 channel,避免告警拥塞) - 订阅:连接后带 JWT;服务端维护
admin_id → socket;仅推送 该 admin 当前 Pin 的 server_id 更新 - 前端:Servers 页 mount 时 connect;unmount disconnect;收到包更新 WatchSlotRow 与 ECharts append
前端组件
| 组件/页 | 职责 |
|---|---|
WatchSlotRow.vue |
StatCardsRow 下 4 卡;gauge + 15min 折线;倒计时 |
WatchSlotCard.vue |
单槽 UI:三环 + ↑出口 ↓入口 + 磁盘读写速率 |
useWatchPins.ts |
Pin CRUD、WS、倒计时 |
WatchMetricsPage.vue |
历史趋势 + 探针记录 Tab(v-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 |
验收标准
- 服务器列表每行有 +,点击后 5s 内 CPU/内存/磁盘% 与 网络出入口速率 连续更新。
- 满 4 槽再点 + 出现替换对话框;手动删除后槽变空。
- 8h 到期自动消失(误差 ≤ 2min);再次 + 同一台重置 8h。
- 未 Pin 的子机:抓包/日志 无 额外 SSH 探针。
#/watch-metrics探针记录 Tab 可见每次 5s 探针(含失败);按 session 筛选与 Pin 时段一致。- SSH 失败:卡槽红态 + DB 有
probe_status=ssh_*行;不显示 0% 冒充正常。 - 模拟 SSH 超时单测:断言 仍有 INSERT 且
cpu_pct IS NULL。 - 探针记录与趋势页均含 disk_pct、net_up_bps、net_down_bps 列/序列;相邻 5s 点带宽差分正确。
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 自愈。