5.7 KiB
5.7 KiB
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 状态] → 拨测是幂等的(无状态定时任务),重启后立即开始新一轮即可,无需恢复状态