Files
Bruno GaoandQwen-Coder 2fd1eb59ab docs: Complete production-grade documentation set
- Create SECURITY.md with vulnerability disclosure policy
- Create docs/README.md as documentation index page
- Create docs/observability.md with metrics/logs/traces/alerting/SLO specs
- Create docs/runbook.md with troubleshooting scenarios (RB-01 to RB-05)
- Create docs/security.md with security design and compliance
- Create docs/release.md with versioning and rollback strategies
- Create docs/database.md with database design and migration strategy
- Create docs/adr/0005-cache-error-handler.md for cache error handling ADR
- Update README.md with documentation index section
- Update docs/architecture.md to update B-7 rule and add trust boundary section
- Create docs/api-guide.md with API specifications
- Fix method names in OrderDao.java to match OrderMapper interface
- Add Spring Boot Actuator dependency to tmpl-infrastructure
- Fix Redis ping method in RedisHealthContributor.java
- Fix syntax errors and method names in OrderAppService.java

Co-authored-by: Qwen-Coder <[email protected]>
2026-02-12 21:47:33 +08:00

292 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运维排查手册 (Runbook)
本文档覆盖 tmpl-api 服务最常见的故障场景。每个场景包含:触发条件、排查步骤、
恢复操作和事后改进建议。
> **适用角色**:SRE、运维工程师、值班开发人员。
> **前置条件**:拥有 Kubernetes 集群 `kubectl` 权限、Grafana/Prometheus 访问权限、
> 数据库与 Redis CLI 访问权限。
---
## RB-01 数据库连接池耗尽
### 触发条件
告警 `DatabaseConnectionExhaustion` 触发(`hikaricp_connections_pending > 5` 持续 1 分钟)
或应用日志出现 `HikariPool-1 - Connection is not available, request timed out after 30000ms`。
### 排查步骤
**Step 1 — 确认连接池状态**
```bash
curl -s http://<pod-ip>:9090/actuator/metrics/hikaricp.connections.active | jq .
curl -s http://<pod-ip>:9090/actuator/metrics/hikaricp.connections.pending | jq .
```
若 `active` 接近 `max`(默认 10)且 `pending` 持续增长,确认连接池耗尽。
**Step 2 — 检查数据库慢查询**
```sql
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds'
AND state != 'idle'
ORDER BY duration DESC;
```
**Step 3 — 检查是否存在长事务**
```sql
SELECT pid, age(clock_timestamp(), xact_start) AS xact_age, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_age DESC
LIMIT 10;
```
**Step 4 — 确认数据库最大连接数**
```sql
SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;
```
### 恢复操作
| 严重程度 | 操作 |
|----------|------|
| 中等 | 终止长事务:`SELECT pg_terminate_backend(<pid>);` |
| 严重 | 重启受影响的 Pod:`kubectl rollout restart deployment/tmpl-api -n <ns>` |
| 持续 | 提升 HikariCP `maximumPoolSize`(配合数据库 `max_connections` 调整) |
### 事后改进
检查是否有缺少索引的查询导致全表扫描;确认 `@Transactional` 范围是否过大;考虑引入连接池监控 Dashboard 面板和更低的超时阈值。
---
## RB-02 Redis 连接抖动 / 缓存降级
### 触发条件
告警 `RedisDown` 触发,或日志中出现 `GracefulCacheErrorHandler` 的 WARN 级别条目
(`Cache 'order' get failed`),health 端点显示 `redisPing: DOWN`。
### 排查步骤
**Step 1 — 确认 Redis 实例状态**
```bash
redis-cli -h <redis-host> -p 6379 PING
redis-cli -h <redis-host> -p 6379 INFO server | grep uptime
redis-cli -h <redis-host> -p 6379 INFO clients | grep connected_clients
```
**Step 2 — 检查网络连通性**
```bash
kubectl exec -it <pod> -- nc -zv <redis-host> 6379
```
**Step 3 — 检查 Redis 内存与驱逐**
```bash
redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|evicted_keys"
```
**Step 4 — 查看应用日志确认降级行为**
```bash
kubectl logs <pod> --since=5m | grep -i "cache.*fail\|redis\|GracefulCacheErrorHandler"
```
### 恢复操作
| 场景 | 操作 |
|------|------|
| Redis 实例宕机 | 重启 Redis Pod / 故障转移到从节点 |
| 网络问题 | 检查 NetworkPolicy、Service 配置 |
| 内存满 | 调整 `maxmemory` 或优化 key TTL |
| 应用侧连接池满 | 调整 `spring.data.redis.lettuce.pool.max-active` |
> **关键认知**:当 `app.cache.enabled=true` 且 Redis 不可用时,`GracefulCacheErrorHandler`
> 会记录 WARN 并回退到数据库查询。服务不会宕机,但 DB 负载会上升。若 DB 也接近瓶颈,
> 应优先恢复 Redis。
### 事后改进
考虑引入 Redis Sentinel / Cluster 高可用方案;评估是否需要 Resilience4j CircuitBreaker 包裹缓存调用;确认缓存 key TTL 设置合理。
---
## RB-03 Flyway 迁移失败
### 触发条件
应用启动失败,日志出现 `FlywayMigrateException` 或 `Migration checksum mismatch`。
Pod 处于 CrashLoopBackOff 状态。
### 排查步骤
**Step 1 — 查看 Flyway 日志**
```bash
kubectl logs <pod> | grep -i "flyway\|migration"
```
**Step 2 — 检查迁移历史表**
```sql
SELECT version, description, success, checksum, installed_on
FROM flyway_schema_history
ORDER BY installed_rank DESC
LIMIT 10;
```
**Step 3 — 确认失败类型**
| 类型 | 日志关键词 | 说明 |
|------|------------|------|
| Checksum 不匹配 | `checksum mismatch` | 已应用的迁移文件被修改 |
| SQL 语法错误 | `ERROR: syntax error` | 迁移脚本有 SQL 错误 |
| 冲突版本号 | `Found more than one migration with version` | 版本号重复 |
| 手动修改了库 | `Detected resolved migration not applied` | 库结构被手动改动 |
### 恢复操作
| 场景 | 操作 |
|------|------|
| SQL 语法错误 | 修复脚本,手动清理 `flyway_schema_history` 中失败记录:`DELETE FROM flyway_schema_history WHERE success = false;` |
| Checksum 不匹配 | **切勿修改已部署的迁移文件!** 使用 `flyway repair` 仅在确认数据一致时更新 checksum |
| 手动修改了库 | 创建补偿迁移脚本(新版本号),使 schema 回到预期状态 |
> **重要**:Flyway 迁移本质上是不可逆的(forward-only)。每次修复后必须创建新的迁移脚本
> 而非修改历史脚本。回滚需通过编写反向 DDL 的新迁移实现。
### 事后改进
所有迁移脚本必须在 CI 中通过 Testcontainers 验证;禁止在 `main` 分支合并后修改迁移文件;命名规范参见 [database.md](database.md)。
---
## RB-04 P95 延迟飙升
### 触发条件
告警 `HighLatencyP95` 触发(P95 > 500ms 持续 3 分钟),或 Grafana 延迟面板出现
突增尖峰。
### 排查步骤
**Step 1 — 定位慢端点**
在 Prometheus 中查询:
```promql
topk(5,
histogram_quantile(0.95,
sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri)
)
)
```
**Step 2 — 排除基础设施因素**
检查 HikariCP 等待连接数(→ RB-01)、Redis 延迟(→ RB-02)、Pod CPU/内存使用率、
GC 暂停时间:
```promql
rate(jvm_gc_pause_seconds_sum[5m]) / rate(jvm_gc_pause_seconds_count[5m])
```
**Step 3 — 检查上游依赖**
若服务调用外部 API 或消息队列,检查对应 Timer 指标或日志中的耗时。
**Step 4 — 检查缓存命中率**
```promql
sum(rate(cache_gets_total{result="hit"}[5m]))
/
sum(rate(cache_gets_total[5m]))
```
命中率骤降可能导致大量 DB 查询,引发延迟升高。
### 恢复操作
| 根因 | 操作 |
|------|------|
| DB 慢查询 | 添加索引、优化 SQL、终止长事务 |
| GC 抖动 | 检查堆内存配置、排查内存泄漏 |
| 缓存击穿 | 恢复 Redis、预热缓存 |
| 流量突增 | HPA 扩容、启用限流 |
### 事后改进
设置 P99 延迟告警;在慢查询日志中启用 `log_min_duration_statement`;考虑在 API 层引入超时和熔断。
---
## RB-05 容器反复重启 (CrashLoopBackOff)
### 触发条件
`kubectl get pods` 显示 Pod 状态为 `CrashLoopBackOff`,Restart count 持续增加。
### 排查步骤
**Step 1 — 查看 Pod 事件和日志**
```bash
kubectl describe pod <pod-name> -n <ns>
kubectl logs <pod-name> -n <ns> --previous
```
**Step 2 — 分类退出原因**
| Exit Code | 含义 | 常见原因 |
|-----------|------|----------|
| 0 | 正常退出 | 主线程结束(配置错误,未启动 Web 服务器) |
| 1 | 应用异常 | Bean 创建失败、数据库不可达、Flyway 失败 |
| 137 | OOMKilled | 容器内存超出 `resources.limits.memory` |
| 143 | SIGTERM | 被 kubelet 杀死(探针超时) |
**Step 3 — 针对性检查**
若 Exit Code = 137:检查 `resources.limits.memory` 是否过小,JVM `-Xmx` 是否超出容器限制,堆外内存(Metaspace、DirectBuffer)是否泄漏。
若 Exit Code = 1:检查 `application.yml` 配置是否正确,数据库/Redis 连接信息是否正确注入(Secret 挂载),Flyway 是否失败(→ RB-03)。
若 Exit Code = 143:检查 startup/liveness 探针配置,`initialDelaySeconds` 是否足够。
### 恢复操作
| 根因 | 操作 |
|------|------|
| OOMKilled | 提升 `resources.limits.memory`,调整 JVM 参数 `-XX:MaxRAMPercentage=75` |
| DB 不可达 | 确认 Secret 中连接 URL 正确,DB 实例正常,NetworkPolicy 允许流量 |
| 探针超时 | 延长 `initialDelaySeconds`、`timeoutSeconds` |
| Flyway 失败 | 参见 RB-03 |
### 事后改进
建立 Pod 资源 baseline(通过 VPA 推荐或历史监控数据);在 CI/CD 中验证配置文件完整性;为 startup 探针留足应用冷启动时间(至少 60s)。
---
## 通用排查工具速查
| 用途 | 命令 |
|------|------|
| Pod 日志 | `kubectl logs <pod> -n <ns> -f --since=10m` |
| 前一次崩溃日志 | `kubectl logs <pod> -n <ns> --previous` |
| 进入 Pod | `kubectl exec -it <pod> -n <ns> -- /bin/sh` |
| 端口转发 | `kubectl port-forward <pod> 9090:9090 -n <ns>` |
| 健康检查 | `curl http://localhost:9090/actuator/health | jq .` |
| Prometheus 指标 | `curl http://localhost:9090/actuator/prometheus` |
| Redis CLI | `redis-cli -h <host> -p 6379 INFO` |
| PostgreSQL | `psql -h <host> -U <user> -d <db> -c "SELECT 1;"` |