131 lines
5.7 KiB
Markdown
131 lines
5.7 KiB
Markdown
## Context
|
||
|
||
Gateway Checker 当前是一个 Bun + React 全栈脚手架,仅包含 demo 验证逻辑(`/api/demo` 端点 + 前端展示连接状态)。项目已有完整的开发、构建、打包、测试链路。需要将其转化为一个可用的 HTTP 拨测工具。
|
||
|
||
现有基础设施:
|
||
- Bun 后端:路由框架(`createFetchHandler`)、服务启动(`startServer`)、运行时配置解析(`readRuntimeConfig`)
|
||
- React 前端:Vite + React + TypeScript,开发期通过 Vite proxy 转发 `/api/*`
|
||
- 构建:Vite 前端构建 + Bun 单 executable 打包
|
||
- 测试:Bun test + smoke test
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
- 提供完整的 HTTP 拨测能力:YAML 配置 → 定时并发拨测 → 结果持久化 → 可视化展示
|
||
- 支持灵活的拨测配置:per-target interval、自定义 method/header/body、expect 校验
|
||
- 前端 Dashboard 实时展示:总览统计、目标状态列表、历史记录、延迟趋势图
|
||
- 保持现有项目架构风格和构建打包链路
|
||
- 零外部运行时依赖新增(仅前端 recharts)
|
||
|
||
**Non-Goals:**
|
||
- 不做告警通知(邮件/短信/Webhook),仅 Dashboard 展示
|
||
- 不做数据自动清理/过期策略,保留全部历史记录
|
||
- 不做 SSE/WebSocket 实时推送,用轮询即可
|
||
- 不做拨测目标动态增删(需修改 YAML 后重启)
|
||
- 不做认证/鉴权
|
||
- 不做分布式/集群部署
|
||
|
||
## Decisions
|
||
|
||
### 1. 配置管理:YAML 统一配置 + 单 CLI 参数
|
||
|
||
**选择**:所有配置(server、数据目录、拨测默认值、目标列表)统一到 YAML 文件,CLI 只接受一个参数即配置文件路径。
|
||
|
||
**替代方案**:
|
||
- CLI 参数 + 环境变量覆盖部分配置 → 配置分散,维护成本高
|
||
- TOML 格式 → Bun 无内置支持,需引入依赖
|
||
|
||
**理由**:
|
||
- 用户明确要求"配置统一到 YAML 文件"
|
||
- `Bun.YAML.parse()` 内置支持,零依赖
|
||
- 单参数 CLI 最简洁:`./gateway-checker ./probes.yaml`
|
||
|
||
### 2. 数据存储:SQLite(bun:sqlite)
|
||
|
||
**选择**:使用 Bun 内置 `bun:sqlite` 模块,WAL 模式运行。
|
||
|
||
**替代方案**:
|
||
- JSONL 文件追加 → 聚合查询需全表扫描,趋势计算复杂
|
||
- 外部 SQLite 库(better-sqlite3)→ bun:sqlite 已内置,无需引入
|
||
|
||
**理由**:
|
||
- 趋势分析需要 `AVG(latency) GROUP BY hour` 等聚合查询,SQL 原生支持
|
||
- bun:sqlite 是 Bun 内置模块,不违反"不引入新依赖"约束
|
||
- WAL 模式支持并发读写
|
||
- 单 `.db` 文件,便于管理
|
||
|
||
### 3. 调度模型:按 interval 分组 + 组内并发
|
||
|
||
**选择**:将所有 target 按其 interval 值分组,每组一个 `setInterval` timer,组内使用 `Promise.all` 并发拨测。
|
||
|
||
**替代方案**:
|
||
- 全局统一 tick → 无法支持 per-target interval
|
||
- 每个 target 独立 timer → 目标多时 timer 数量大,资源浪费
|
||
- 使用调度队列(如 BullMQ)→ 过度设计
|
||
|
||
**理由**:
|
||
- 支持 per-target interval,满足不同服务不同频率的需求
|
||
- 相同 interval 的目标共享 timer,timer 数量 = 不同 interval 值的数量
|
||
- 组内并发保证批量效率,组间隔离互不影响
|
||
|
||
### 4. 前端更新策略:轮询
|
||
|
||
**选择**:前端每 5-10 秒轮询 `/api/summary` 和 `/api/targets`。
|
||
|
||
**替代方案**:
|
||
- SSE 服务端推送 → 实现复杂,拨测间隔 15-60s 级别无必要
|
||
- WebSocket → 更复杂,过度设计
|
||
|
||
**理由**:
|
||
- 拨测间隔本身是 15-60s,5s 轮询延迟完全可接受
|
||
- 实现简单,无需维护长连接状态
|
||
- Dashboard 面板按需加载趋势数据(展开详情时请求)
|
||
|
||
### 5. 趋势图:recharts
|
||
|
||
**选择**:引入 recharts 作为前端图表库。
|
||
|
||
**替代方案**:
|
||
- 纯 SVG 手写 sparkline → 零依赖但代码量大,交互能力有限
|
||
- Chart.js → 非 React 原生,需要 wrapper
|
||
- D3 → 过于底层
|
||
|
||
**理由**:
|
||
- 用户确认允许引入轻量图表库
|
||
- recharts 是 React 原生图表库,与现有 React 技术栈一致
|
||
- 支持折线图、迷你 Sparkline,满足需求
|
||
- 社区活跃,文档完善
|
||
|
||
### 6. 目标状态判定模型
|
||
|
||
**选择**:两层判定——`success`(请求是否完成)+ `matched`(是否符合 expect 规则)。
|
||
|
||
```
|
||
● UP = success ✓ && matched ✓
|
||
● DOWN = !success || !matched
|
||
```
|
||
|
||
**理由**:
|
||
- 区分"网络不可达"和"返回了非预期状态码"两种故障场景
|
||
- expect 规则可选,不配置时 matched 默认为 true
|
||
- 前端可以根据 `success`/`matched` 分别展示不同故障原因
|
||
|
||
### 7. 数据库 Schema 设计
|
||
|
||
**targets 表**:从 YAML 同步初始化,运行时只读。
|
||
**check_results 表**:只追加写入,索引 `(target_id, timestamp)` 加速历史查询。
|
||
|
||
**理由**:
|
||
- targets 从 YAML 来,不提供运行时动态增删(符合 Non-Goals)
|
||
- check_results 追加写入,无需更新/删除,简单可靠
|
||
- 按时间范围查询是最高频操作,复合索引覆盖
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **[YAML 格式错误导致启动失败]** → 解析时做完整校验,输出清晰错误信息(字段缺失、格式不对、值非法等),提前失败而非运行时出错
|
||
- **[并发拨测对目标服务器压力]** → 每组内 Promise.all 并发,但同一 group 的 tick 间隔内不会重复拨测。如果用户配置了大量目标且 interval 很短,可能对目标产生压力,这是用户配置责任
|
||
- **[SQLite 数据文件增长]** → 当前不清理,长期运行会增长。预留清理策略接口,后续可通过配置保留天数
|
||
- **[recharts 包体积]** → recharts gzip 后约 70KB,会增加前端 bundle 大小。对于内部工具可接受
|
||
- **[拨测请求超时阻塞]** → 使用 `AbortController` + `setTimeout` 实现超时,避免单个慢请求阻塞整组
|
||
- **[进程重启后丢失 timer 状态]** → 拨测是幂等的(无状态定时任务),重启后立即开始新一轮即可,无需恢复状态
|