基于全景审计分析,产出 5 份跨领域设计规格: 1. 性能优化 — 后端批量INSERT/合并COUNT/告警预加载 + 前端N+1内联name 2. 安全纵深防御 — PostgreSQL RLS/行级数据范围/session_key Redis/审计哈希链 3. 事件驱动架构增强 — 6个业务域11个缺失事件补发 + Outbox LISTEN/NOTIFY 4. 前端工程化 — 14个大组件拆分 + 3个重复模式统一 + Bundle优化 5. 可观测性与运维 — 深度健康检查/Prometheus/OpenTelemetry/生产Docker
216 lines
9.1 KiB
Markdown
216 lines
9.1 KiB
Markdown
# 可观测性与运维基础设施设计
|
||
|
||
> 日期: 2026-04-26 | 状态: draft | 主题: 健康检查、Prometheus 指标、分布式追踪、生产 Docker、日志聚合
|
||
|
||
## 1. 背景
|
||
|
||
HMS 后端基于 Axum 0.8 + SeaORM 1.1 + Redis 0.27 构建,当前运维能力缺口:
|
||
|
||
| 能力 | 现状 | 差距 |
|
||
|------|------|------|
|
||
| 结构化日志 | tracing + tracing-subscriber JSON 格式 | 已实现,无聚合方案 |
|
||
| 健康检查 | GET /health 返回 { status, version, modules } | 不验证 DB/Redis 连通性 |
|
||
| 指标暴露 | 无 Prometheus endpoint | 需从零搭建 |
|
||
| 分布式追踪 | 无 OpenTelemetry | 需从零搭建 |
|
||
| 生产 Docker | 仅有开发 docker-compose(PostgreSQL + Redis) | 无 Rust 应用 Dockerfile |
|
||
| 日志聚合 | 无 ELK/Loki 集成 | 需从零搭建 |
|
||
|
||
技术栈:tower-http 0.6(已启用 trace feature)、自定义 rate_limit/JWT 中间件通过 `axum::middleware::from_fn` 注册。
|
||
|
||
## 2. 问题分析
|
||
|
||
### 2.1 健康检查不充分
|
||
|
||
当前 `/health` 仅返回静态信息(版本号、模块名列表),不验证外部依赖连通性。容器编排无法据此判断服务是否真正可用。
|
||
|
||
### 2.2 无可观测性指标
|
||
|
||
缺少请求延迟分布(P50/P95/P99)、错误率、QPS、DB 连接池使用率、事件 outbox 积压量等关键运行指标。
|
||
|
||
### 2.3 无分布式追踪
|
||
|
||
Axum handler -> SeaORM query -> Redis command 之间无 trace_id 串联。排查跨模块问题(预约创建 -> 工作流启动 -> 消息通知)需手动对齐日志时间戳。
|
||
|
||
### 2.4 无生产级容器镜像
|
||
|
||
Rust 应用直接 `cargo run` 启动,缺少多阶段构建 Dockerfile、健康检查指令、非 root 用户运行。
|
||
|
||
## 3. 解决方案
|
||
|
||
### 3.1 深度健康检查
|
||
|
||
**Crate 选型**: 无额外依赖,使用已有的 sea_orm + redis。
|
||
|
||
**改造方案**: 扩展 `HealthResponse` 为分级检查,增加 `/health/live`(存活探针)和 `/health/ready`(就绪探针)两个子路径。
|
||
|
||
检查项:
|
||
|
||
| 组件 | 检查方式 | 超时 | 关键性 |
|
||
|------|---------|------|--------|
|
||
| PostgreSQL | `SELECT 1` via SeaORM | 2s | 关键(失败返回 503) |
|
||
| Redis | `PING` via redis::Client | 1s | 非关键(失败标记 degraded) |
|
||
| 模块状态 | 遍历 registry 检查 on_startup 是否完成 | 0ms | 非关键 |
|
||
|
||
状态判定:全部通过 → `healthy`,非关键组件失败 → `degraded`(200),关键组件失败 → `unhealthy`(503)。
|
||
|
||
**对现有代码的影响**: 仅修改 `handlers/health.rs`(~40 行改动),`AppState` 无需变化。
|
||
|
||
### 3.2 Prometheus 指标
|
||
|
||
**Crate 选型**: `metrics` 0.24 + `metrics-exporter-prometheus` 0.16
|
||
|
||
选择理由:`metrics` 是 Rust 生态的指标门面 crate(类似 `log`/`tracing` 的解耦设计),exporter 内置独立 HTTP server,不侵入 Axum 路由。
|
||
|
||
**指标设计:**
|
||
|
||
| 类别 | 指标名 | 类型 |
|
||
|------|--------|------|
|
||
| 请求 | `http_request_duration_seconds{method,path,status}` | histogram |
|
||
| 请求 | `http_requests_total{method,path,status}` | counter |
|
||
| 数据库 | `db_pool_connections{state}` | gauge |
|
||
| 数据库 | `db_query_duration_seconds{operation}` | histogram |
|
||
| 事件 | `eventbus_published_total{event_type}` | counter |
|
||
| 事件 | `eventbus_outbox_pending_count` | gauge |
|
||
| 运行时 | `process_memory_rss_bytes` | gauge |
|
||
|
||
**Axum middleware 集成要点:**
|
||
|
||
- 新增 `middleware/metrics.rs`(~40 行),记录每个请求的 method/归一化 path/status/耗时
|
||
- 路径归一化:`/api/v1/patients/xxx` → `/api/v1/patients/:id`,避免高基数标签
|
||
- main.rs 初始化 exporter 监听独立端口 9090
|
||
- 在路由组装处添加 `.layer(axum_middleware::from_fn(metrics_middleware))`
|
||
|
||
**对现有代码的影响**: main.rs ~10 行、新增 middleware ~40 行、outbox/event_bus 关键路径埋点 ~20 行。
|
||
|
||
### 3.3 OpenTelemetry 分布式追踪
|
||
|
||
**Crate 选型**: `opentelemetry` 0.27 + `opentelemetry-otlp` 0.27 + `tracing-opentelemetry` 0.28
|
||
|
||
**成熟度评估:**
|
||
- Rust OTel SDK 0.27+ 版本 API 趋于稳定
|
||
- `tracing-opentelemetry` 与 tracing-subscriber 兼容性良好
|
||
- SeaORM/Redis 无原生 span 支持,需手动埋点
|
||
- 风险:SDK 初始化增加启动时间约 100-200ms
|
||
|
||
**集成方案:**
|
||
|
||
1. main.rs tracing 初始化重构为条件启用:通过 `OTEL_EXPORTER_OTLP_ENDPOINT` 环境变量决定是否启用
|
||
2. 利用已有的 `tower-http` TraceLayer(项目已依赖)注入 trace_id
|
||
3. SeaORM 关键查询点手动创建 `tracing::info_span!("db.query", db.operation = "xxx")`
|
||
4. 采用 OTLP 协议导出,兼容 Jaeger/Tempo/Zipkin
|
||
|
||
**对现有代码的影响**: main.rs ~30 行改动、Cargo.toml 新增 4 个依赖、service 函数 span 注解渐进式添加。
|
||
|
||
### 3.4 生产 Docker 镜像
|
||
|
||
**多阶段构建策略:**
|
||
|
||
| 阶段 | 基础镜像 | 目的 |
|
||
|------|---------|------|
|
||
| 编译 | rust:1.85-bookworm | cargo build --release |
|
||
| 运行 | debian:bookworm-slim | 仅二进制 + ca-certificates |
|
||
|
||
关键设计:
|
||
- **层缓存优化**: 先复制所有 Cargo.toml → 创建空源文件 → 编译依赖 → 复制实际源码 → 编译应用。依赖不变时复用编译缓存。
|
||
- **安全**: 非 root 用户(erp:erp)运行
|
||
- **健康检查**: `curl -f http://localhost:3000/health/live`
|
||
- **预期运行时镜像**: ~50-80MB
|
||
|
||
**docker-compose.prod.yml**: erp-server 服务 + 环境变量注入 + depends_on health condition + 健康检查指向 `/health/ready`。
|
||
|
||
### 3.5 日志聚合
|
||
|
||
**方案: Grafana Loki**
|
||
|
||
选择理由:与 Prometheus 同属 Grafana 生态,不做全文索引按标签查询,资源消耗远低于 ELK。tracing-subscriber JSON 输出天然兼容 Loki 标签模型。
|
||
|
||
部署:Loki 3.0 + Grafana 11.0 + Prometheus 2.52 通过 `docker-compose.monitoring.yml` 独立管理。日志采集通过 Grafana Alloy 收集 Docker 容器 stdout。
|
||
|
||
### 3.6 告警规则
|
||
|
||
基于 Prometheus 指标的 5 条核心告警:
|
||
|
||
| 规则 | 条件 | 级别 |
|
||
|------|------|------|
|
||
| HighErrorRate | 5xx 比率 > 5% 持续 2m | critical |
|
||
| HighLatencyP99 | P99 > 2s 持续 5m | warning |
|
||
| DatabasePoolExhaustion | 连接池使用率 > 85% 持续 3m | warning |
|
||
| OutboxBacklog | outbox 积压 > 100 持续 5m | warning |
|
||
| HealthCheckFailed | 服务 up == 0 持续 1m | critical |
|
||
|
||
## 4. 实施步骤
|
||
|
||
### Phase 1: 深度健康检查 + 生产 Docker(1-2 天)
|
||
|
||
| 任务 | 改动范围 | 优先级 |
|
||
|------|---------|--------|
|
||
| 扩展 HealthResponse + DB/Redis 检查 | handlers/health.rs ~60 行 | P0 |
|
||
| 添加 /health/live 和 /health/ready | handlers/health.rs ~20 行 | P0 |
|
||
| 编写生产 Dockerfile | 新文件 ~50 行 | P0 |
|
||
| 编写 docker-compose.prod.yml | 新文件 ~40 行 | P0 |
|
||
|
||
### Phase 2: Prometheus 指标(2 天)
|
||
|
||
| 任务 | 改动范围 | 优先级 |
|
||
|------|---------|--------|
|
||
| 引入 metrics crate | Cargo.toml ~4 行 | P0 |
|
||
| 实现 metrics middleware | 新文件 ~40 行 | P0 |
|
||
| 注册 middleware + exporter 初始化 | main.rs ~15 行 | P0 |
|
||
| SeaORM/EventBus 指标埋点 | ~40 行 | P1 |
|
||
| Prometheus + Grafana Docker 配置 | 新文件 ~60 行 | P1 |
|
||
|
||
### Phase 3: OpenTelemetry 集成(2-3 天)
|
||
|
||
| 任务 | 改动范围 | 优先级 |
|
||
|------|---------|--------|
|
||
| 引入 opentelemetry crate | Cargo.toml ~6 行 | P2 |
|
||
| 重构 tracing 初始化为条件启用 | main.rs ~30 行 | P2 |
|
||
| 添加 TraceLayer | main.rs ~5 行 | P2 |
|
||
| Service 函数 span 注解 | 渐进式 | P2 |
|
||
|
||
### Phase 4: 日志聚合 + 告警(1-2 天)
|
||
|
||
| 任务 | 改动范围 | 优先级 |
|
||
|------|---------|--------|
|
||
| Loki + Grafana 部署配置 | 新文件 ~40 行 | P2 |
|
||
| Grafana Alloy 日志采集配置 | 新文件 ~30 行 | P2 |
|
||
| Prometheus 告警规则 | 新文件 ~50 行 | P2 |
|
||
|
||
## 5. 风险与缓解
|
||
|
||
| 风险 | 影响 | 缓解措施 |
|
||
|------|------|---------|
|
||
| OTel SDK breaking change | 升级困难 | 锁定 0.27 版本,feature flag 条件启用 |
|
||
| 指标收集增加延迟 | 性能退化 | histogram 无锁实现,单次 record < 50ns |
|
||
| 日志量导致存储膨胀 | 存储成本 | Loki retention 30 天,JSON 压缩率高 |
|
||
| Docker 编译缓存失效 | CI 时间长 | Cargo.toml 层和源码层分离 |
|
||
| Prometheus 暴露内部信息 | 安全风险 | 独立端口 9090,网络策略限制访问 |
|
||
| 健康检查超时阻塞 | /health 延迟 | 短超时(DB 2s/Redis 1s),并行检查 |
|
||
|
||
**Crate 选型对比:**
|
||
|
||
| 方案 | 优势 | 劣势 | 结论 |
|
||
|------|------|------|------|
|
||
| `prometheus` crate (原生) | 功能完整 | API 较重 | 不选 |
|
||
| `metrics` + exporter | 轻量 facade,解耦 | 需额外 crate | 推荐 |
|
||
| Jaeger 直接导出 | 简单 | 已废弃 | 不选 |
|
||
| OTLP + Tempo/Jaeger | 通用标准 | 需 Collector | 推荐 |
|
||
|
||
**性能影响评估:**
|
||
|
||
| 组件 | 额外延迟 | 额外内存 | 启动时间增幅 |
|
||
|------|---------|---------|-------------|
|
||
| Prometheus middleware | < 0.1ms/req | ~5MB | < 50ms |
|
||
| OpenTelemetry (10% 采样) | < 0.5ms/req | ~20MB | 100-200ms |
|
||
| 健康检查 (DB ping) | 仅 /health | 无 | 无 |
|
||
|
||
**成功指标:**
|
||
|
||
| 指标 | 当前值 | 目标值 |
|
||
|------|--------|--------|
|
||
| /health 覆盖外部依赖 | 无 | DB + Redis |
|
||
| Prometheus 端点 | 无 | :9090/metrics |
|
||
| 分布式追踪 | 无 | 请求→DB→Redis 全链路 |
|
||
| 生产镜像大小 | 无 | < 80MB |
|
||
| 告警规则数 | 0 | >= 5 条 |
|