# UMS 站点可靠性工程(SRE)全面解决方案 > 版本:v1.0 | 日期:2026-04-05 | 审查人:SRE 工程师 --- ## 执行摘要 本报告对用户管理系统(UMS)进行了全面的 SRE 审查,涵盖**可靠性基线、可观察性成熟度、告警体系、混沌工程能力、容量规划和自动化运维**六大维度。 **当前综合可靠性评级:⚠️ 4.5/10(开发就绪,生产未就绪)** | 维度 | 当前分 | 目标分 | 优先级 | |------|--------|--------|--------| | SLO 定义 | 0/10 | 8/10 | 🔴 P0 | | 可观察性成熟度 | 3/10 | 8/10 | 🔴 P0 | | 告警体系 | 4/10 | 8/10 | 🔴 P0 | | 错误预算管理 | 0/10 | 7/10 | 🔴 P0 | | 混沌工程 | 1/10 | 6/10 | 🟡 P1 | | 容量规划 | 2/10 | 7/10 | 🟡 P1 | | 运维自动化 | 3/10 | 8/10 | 🟡 P1 | --- ## 一、系统架构现状审查 ### 1.1 架构拓扑 ``` ┌─────────────────────────────────────────────────┐ │ 前端层 │ │ React 18 + TypeScript + Ant Design 5 │ │ (Vite 构建, 无 SSR) │ └──────────────────────┬──────────────────────────┘ │ HTTP/REST ┌──────────────────────▼──────────────────────────┐ │ API 层 │ │ Gin HTTP Server (port 8080) │ │ • 认证中间件 • 速率限制中间件 │ │ • IP 过滤中间件 • 操作日志中间件 │ └──────────┬──────────────────────┬───────────────┘ │ │ ┌──────────▼────────┐ ┌─────────▼──────────────┐ │ 业务层 (Service) │ │ 缓存层 │ │ • AuthService │ │ L1: 内存 LRU (10000项) │ │ • UserService │ │ L2: Redis (可选, 未启用) │ │ • DeviceService │ └────────────────────────┘ │ • 异常检测器 │ └──────────┬────────┘ │ ┌──────────▼────────────────────────────────────┐ │ 数据层 │ │ SQLite (当前运行时, 生产需迁移至 PostgreSQL) │ │ GORM ORM │ └───────────────────────────────────────────────┘ ``` ### 1.2 已有可靠性能力(正向) | 能力 | 现状 | |------|------| | 健康检查端点 | ✅ `/health`, `/health/live`, `/health/ready` | | Prometheus 指标 | ✅ 已定义 metrics.go,但**未接入路由暴露** | | Alertmanager 配置 | ✅ 告警规则文件存在,但依赖占位符 | | Grafana 仪表盘 | ✅ JSON 文件存在 | | 优雅关闭 | ✅ 15s 超时 + Webhook 专属5s | | 速率限制 | ✅ 登录/注册/API 三级限流 | | 异常检测 | ✅ AnomalyDetector 已接线 | | Token 轮换 | ✅ Refresh Token 滚动轮换 | | 操作日志 | ✅ 中间件级别审计日志 | | 数据库备份演练 | ✅ 脚本已存在 | ### 1.3 严重可靠性问题(负向) --- ## 二、严重问题审查清单 ### 🔴 CRIT-01:Prometheus 指标端点未接入路由 **问题描述:** `metrics.go` 中定义了完整的 Prometheus 指标,但 `main.go` 和 `router.go` 中**没有注册 `/metrics` 端点**。监控系统实际上收集不到任何数据。 ```go // main.go 中缺失: // engine.GET("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) // 当前 /health 只返回 {"status":"ok"},没有 Prometheus 格式指标 ``` **影响:** Alertmanager 告警规则形同虚设,Grafana 仪表盘无数据,所有监控告警全部失效。 **修复优先级:** P0 — 必须立即修复 --- ### 🔴 CRIT-02:PrometheusMiddleware 未挂载到路由 **问题描述:** `monitoring/middleware.go` 中定义了 `PrometheusMiddleware`,但 `router.go` 的 `Setup()` 方法中**没有调用**,HTTP 请求计数和延迟指标全部为零。 **影响:** `HighErrorRate`、`HighResponseTime`、`UnusualAPIRequestRate` 三个核心告警永远不会触发。 **修复优先级:** P0 --- ### 🔴 CRIT-03:SLO 完全缺失 **问题描述:** 系统没有定义任何 SLO(服务级别目标)。没有 SLO 意味着: - 不知道什么样的错误率是"可接受"的 - 错误预算无法计算,无法指导发布决策 - 告警阈值缺乏业务依据(当前 5% 错误率阈值是拍脑袋来的) **影响:** 整个可靠性工程体系缺少地基。 **修复优先级:** P0 --- ### 🔴 CRIT-04:仅邮件告警,无 On-Call 升级链路 **问题描述:** `alertmanager.yml` 中只配置了 email_configs,且收件人地址全是占位符 `${ALERTMANAGER_CRITICAL_TO}`。生产环境: - 无即时通知渠道(钉钉/飞书/PagerDuty/企业微信) - 无 On-Call 轮班配置 - Critical 告警和 Warning 告警都发邮件,无差异化响应 **影响:** 凌晨 3 点系统宕机,值班工程师无法被及时叫醒。 **修复优先级:** P0 --- ### 🔴 CRIT-05:SQLite 用于运行时(单点故障) **问题描述:** 当前 `config.yaml` 配置为 SQLite,这意味着: - 无主从复制,无读写分离 - 写操作串行化(WAL 模式下并发受限) - 无法水平扩展 - 文件级单点故障 **影响:** 任何磁盘故障或进程崩溃都会导致完全不可用(SPOF)。 **修复优先级:** P0(生产上线前必须迁移至 PostgreSQL) --- ### 🟡 WARN-01:L1 Cache updateAccessOrder 时间复杂度 O(n) **问题描述:** `l1.go` 中 `updateAccessOrder` 方法使用线性扫描,时间复杂度为 O(n)。当缓存接近 10000 条目时,每次缓存读取都会触发最坏 O(10000) 遍历。 ```go // 当前实现:O(n) 线性扫描 func (c *L1Cache) updateAccessOrder(key string) { for i, k := range c.accessOrder { // 最坏 O(10000) 次遍历 if k == key { ... } } } ``` **影响:** 高并发下缓存层成为性能瓶颈,延迟 P99 显著上升。 **修复优先级:** P1 — 应改用 container/list 双向链表 + map 实现 O(1) LRU --- ### 🟡 WARN-02:健康检查未检查 Redis 连接 **问题描述:** `health.go` 的 `Check()` 方法只检查数据库,没有检查 Redis 连接状态(当 L2 Cache 启用时)。Redis 故障会导致缓存降级,但健康检查仍返回 UP。 **修复优先级:** P1 --- ### 🟡 WARN-03:Webhook 服务 Enabled 硬编码为 false **问题描述:** `main.go` 中: ```go webhookService := service.NewWebhookService(db.DB, service.WebhookServiceConfig{ Enabled: false, // ← 硬编码!config.yaml 中 webhook.enabled=true 被忽略 }) ``` **影响:** Webhook 功能实际上完全禁用,与配置文件不一致。 **修复优先级:** P1 --- ### 🟡 WARN-04:缺少分布式追踪(Tracing) **问题描述:** `config.yaml` 中 `monitoring.tracing.enabled: false`,系统完全没有链路追踪能力。当一个请求经过多个 Service 时,无法追踪请求路径。 **影响:** 排查跨 Service 问题时,平均恢复时间(MTTR)会大幅增加。 **修复优先级:** P1 --- ### 🟡 WARN-05:结构化日志未完整实现 **问题描述:** `config.yaml` 定义了 JSON 格式日志,但实际代码中大量使用 `log.Printf`(Go 标准库),不携带 trace_id、request_id、user_id 等上下文字段。 **影响:** 日志无法有效聚合查询,排障困难。 **修复优先级:** P1 --- ### 🟢 INFO-01:速率限制 Map 无界增长(历史遗留) **问题描述:** 历史代码审查记录中曾提及 Rate limiter map 无界限增长风险。需确认当前实现是否已修复。 --- ## 三、SLO 定义与错误预算 ### 3.1 SLO 框架 ```yaml # ums-slo.yaml - 用户管理系统服务级别目标 service: user-management-system owner: platform-team review_cycle: 30d slos: # SLO-1: API 可用性 - name: api-availability description: "有效 HTTP 请求返回非 5xx 响应的比例" sli: metric: | ( sum(rate(http_requests_total{status!~"5.."}[5m])) / sum(rate(http_requests_total[5m])) ) target: 99.9% # 每月允许约 43.8 分钟不可用 window: 30d error_budget_minutes: 43.8 # 每月错误预算 burn_rate_alerts: - name: fast-burn-critical severity: critical short_window: 5m long_window: 1h burn_rate_factor: 14.4 # 1小时内消耗 2% 错误预算 page: true - name: slow-burn-warning severity: warning short_window: 30m long_window: 6h burn_rate_factor: 6 # 6小时内消耗 5% 错误预算 page: false # SLO-2: API 响应延迟 - name: api-latency description: "P99 请求延迟 < 500ms 的请求比例" sli: metric: | ( sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m])) / sum(rate(http_request_duration_seconds_count[5m])) ) target: 99% window: 30d critical_paths: - path: "/api/v1/auth/login" target: 99.5% latency_p99: 300ms - path: "/api/v1/auth/refresh" target: 99.9% latency_p99: 100ms burn_rate_alerts: - name: latency-fast-burn severity: warning short_window: 5m long_window: 1h burn_rate_factor: 14.4 # SLO-3: 登录成功率 - name: login-success-rate description: "登录请求成功(非系统错误)的比例" sli: metric: | ( sum(rate(user_logins_total{status="success"}[5m])) / sum(rate(user_logins_total[5m])) ) target: 99% window: 30d notes: "暴力破解导致的合理失败不计入 SLO 违规" # SLO-4: 数据库查询延迟 - name: db-query-latency description: "P95 数据库查询延迟 < 100ms 的比例" sli: metric: | histogram_quantile(0.95, sum(rate(db_query_duration_seconds_bucket[5m])) by (le, operation) ) < 0.1 target: 95% window: 30d ``` ### 3.2 错误预算政策 ``` ┌─────────────────────────────────────────────────────┐ │ 错误预算消耗策略 │ ├─────────────────────────────────────────────────────┤ │ 预算剩余 > 50%:正常发布,可以快速迭代 │ │ 预算剩余 25-50%:评审每次发布风险,加强测试 │ │ 预算剩余 10-25%:冻结非关键功能发布,集中修复可靠性 │ │ 预算剩余 < 10%:仅允许可靠性修复发布,启动事后审查 │ │ 预算已耗尽:停止所有功能发布,直到下个周期 │ └─────────────────────────────────────────────────────┘ ``` --- ## 四、可观察性补强方案 ### 4.1 三大支柱现状 vs 目标 | 支柱 | 现状 | 目标 | 差距 | |------|------|------|------| | **指标** | 已定义但未暴露 | 完整 Prometheus + Grafana | 接入路由 + 补充业务指标 | | **日志** | 标准库 log.Printf | 结构化 JSON + 上下文字段 | 引入 slog/zap + 字段标准化 | | **追踪** | 完全缺失 | OpenTelemetry 链路追踪 | 全量接入 | ### 4.2 指标补强清单 **当前缺失的关键指标:** ```go // 需要新增的 Prometheus 指标 var ( // 错误预算消耗速率(直接从 SLO 派生) errorBudgetBurnRate = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "error_budget_burn_rate", Help: "Current error budget burn rate multiplier", }, []string{"slo"}, ) // 缓存命中率(告警规则引用此指标,但当前未定义) cacheHitsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "cache_hits_total", Help: "Total cache hits", }, []string{"level", "operation"}, // level: l1/l2 ) cacheOperationsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "cache_operations_total", Help: "Total cache operations", }, []string{"level", "operation"}, ) // 数据库连接池状态(告警引用但未定义) dbConnectionsActive = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "db_connections_active", Help: "Active database connections", }, ) dbConnectionsMax = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "db_connections_max", Help: "Maximum database connections", }, ) // 令牌刷新操作 tokenRefreshTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "token_refresh_total", Help: "Total token refresh attempts", }, []string{"status"}, // success/failure/rate_limited ) // 账号锁定事件 accountLockTotal = prometheus.NewCounter( prometheus.CounterOpts{ Name: "account_lock_total", Help: "Total account lockout events", }, ) // 异常登录检测 anomalyDetectedTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "anomaly_detected_total", Help: "Total anomaly login detections", }, []string{"type"}, // geo_anomaly/device_anomaly/brute_force ) ) ``` ### 4.3 结构化日志方案 **日志字段标准:** ```go // 每条日志必须携带的上下文字段 type LogContext struct { TraceID string `json:"trace_id"` // OpenTelemetry trace SpanID string `json:"span_id"` RequestID string `json:"request_id"` // X-Request-ID header UserID string `json:"user_id,omitempty"` IP string `json:"ip"` Method string `json:"method"` Path string `json:"path"` Duration int64 `json:"duration_ms"` Status int `json:"status"` Error string `json:"error,omitempty"` } // 安全事件专用字段 type SecurityLogEvent struct { EventType string `json:"event_type"` // login_failed/brute_force/anomaly Severity string `json:"severity"` // low/medium/high/critical UserID string `json:"user_id,omitempty"` IP string `json:"ip"` DeviceID string `json:"device_id,omitempty"` Details string `json:"details"` } ``` **推荐接入 `log/slog`(Go 1.21+):** ```go // 替换 log.Printf → slog import "log/slog" // 初始化结构化 logger logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, AddSource: false, })) slog.SetDefault(logger) // 在 Gin middleware 中注入 request_id func StructuredLogger() gin.HandlerFunc { return func(c *gin.Context) { requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() } c.Set("request_id", requestID) c.Header("X-Request-ID", requestID) start := time.Now() c.Next() slog.Info("http_request", "request_id", requestID, "method", c.Request.Method, "path", c.FullPath(), "status", c.Writer.Status(), "duration_ms", time.Since(start).Milliseconds(), "ip", c.ClientIP(), "user_id", c.GetString("user_id"), ) } } ``` ### 4.4 OpenTelemetry 分布式追踪接入 ```go // 最小化追踪接入方案 import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/trace" ) func initTracing(endpoint string, serviceName string) (func(), error) { exporter, err := otlptracehttp.New(context.Background(), otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), ) if err != nil { return nil, err } tp := trace.NewTracerProvider( trace.WithBatcher(exporter), trace.WithSampler(trace.ParentBased(trace.TraceIDRatioBased(0.1))), // 10% 采样 ) otel.SetTracerProvider(tp) return func() { tp.Shutdown(context.Background()) }, nil } ``` --- ## 五、告警体系优化 ### 5.1 告警分级矩阵 | 级别 | 定义 | 响应时间 | 通知渠道 | 示例 | |------|------|----------|----------|------| | **P0-CRITICAL** | 服务完全不可用,影响所有用户 | 5分钟内 | 电话 + 飞书 + 短信 | 健康检查失败、数据库宕机 | | **P1-CRITICAL** | 核心功能降级,错误预算快速燃烧 | 15分钟内 | 飞书 + 短信 | 登录成功率 < 95%、P99 > 2s | | **P2-WARNING** | 性能下降,错误预算缓慢消耗 | 1小时内 | 飞书 | 缓存命中率低、内存 > 80% | | **P3-INFO** | 趋势异常,需要关注 | 工作时间内 | 邮件 | 在线用户异常、API 量异常 | ### 5.2 基于错误预算的燃烧率告警(替代当前阈值告警) **当前问题:** `alerts.yml` 中的告警基于固定阈值(如"错误率 > 5%"),这种方式有两个问题: 1. **误报多**:短暂流量抖动就触发告警,导致告警疲劳 2. **漏报多**:长期小幅度超标会耗尽错误预算,但不触发告警 **改进方案:使用燃烧率(Burn Rate)告警** ```yaml # 改进后的 alerts.yml - 基于 SLO 燃烧率 groups: - name: ums-slo-burn-rate rules: # === SLO-1: API 可用性 燃烧率告警 === # 快速燃烧:1小时消耗 2% 月度错误预算 → 立即告警 - alert: APIAvailability_FastBurn expr: | ( sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) ) > (1 - 0.999) * 14.4 AND ( sum(rate(http_requests_total{status=~"5.."}[1h])) / sum(rate(http_requests_total[1h])) ) > (1 - 0.999) * 14.4 for: 2m labels: severity: critical slo: api-availability page: "true" annotations: summary: "🔴 API 可用性 SLO 快速燃烧 — 立即响应" description: | 错误预算正在以 14.4x 速率消耗(正常速率的14倍) 当前错误率: {{ $value | humanizePercentage }} 若持续1小时,将消耗本月 2% 错误预算 剩余错误预算: 见 Grafana 仪表盘 运维手册: https://docs/runbook/api-availability # 慢速燃烧:6小时消耗 5% 月度错误预算 → 警告 - alert: APIAvailability_SlowBurn expr: | ( sum(rate(http_requests_total{status=~"5.."}[30m])) / sum(rate(http_requests_total[30m])) ) > (1 - 0.999) * 6 AND ( sum(rate(http_requests_total{status=~"5.."}[6h])) / sum(rate(http_requests_total[6h])) ) > (1 - 0.999) * 6 for: 15m labels: severity: warning slo: api-availability page: "false" annotations: summary: "🟡 API 可用性 SLO 缓慢燃烧 — 需要关注" description: | 错误预算正在以 6x 速率消耗 若持续6小时,将消耗本月 5% 错误预算 # === SLO-2: 延迟 燃烧率告警 === - alert: APILatency_FastBurn expr: | histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le) ) > 0.5 * 14.4 for: 2m labels: severity: critical slo: api-latency page: "true" annotations: summary: "🔴 API 延迟 SLO 快速燃烧" description: "P99 延迟: {{ $value }}s,超过 SLO 阈值 500ms" # === 基础设施告警(保留阈值型) === - alert: ServiceDown expr: up{job="user-management"} == 0 for: 1m labels: severity: critical page: "true" annotations: summary: "🚨 服务实例宕机" description: "{{ $labels.instance }} 已离线超过 1 分钟" - alert: DatabaseDown expr: | sum(rate(http_requests_total{status="503"}[2m])) > 0 for: 1m labels: severity: critical page: "true" annotations: summary: "🚨 数据库连接失败" - alert: HighLoginFailureRate_BruteForce expr: | sum(rate(user_logins_total{status="failed"}[5m])) / sum(rate(user_logins_total[5m])) > 0.5 for: 3m labels: severity: critical category: security annotations: summary: "🔐 疑似暴力破解攻击" description: "登录失败率: {{ $value | humanizePercentage }},超过 50%" - alert: TokenRefreshFailureSpike expr: | sum(rate(token_refresh_total{status="failure"}[5m])) > 10 for: 2m labels: severity: warning category: auth annotations: summary: "Token 刷新失败激增" - alert: AnomalyDetectionSpike expr: | sum(rate(anomaly_detected_total[5m])) > 5 for: 2m labels: severity: warning category: security annotations: summary: "异常登录检测激增,可能存在攻击" ``` ### 5.3 多通道告警接收配置 ```yaml # alertmanager.yml 优化版(支持飞书 + 企业微信 + 邮件) global: resolve_timeout: 5m slack_api_url: '${ALERTMANAGER_SLACK_API_URL}' route: group_by: ['alertname', 'slo', 'category'] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: 'default' routes: # P0: 立即叫醒(飞书 + 短信) - match: page: "true" receiver: 'oncall-page' group_wait: 10s repeat_interval: 1h continue: true # 安全事件:安全团队专属通道 - match: category: security receiver: 'security-team' group_wait: 30s continue: true # Warning:告警群组 - match: severity: warning receiver: 'warning-channel' continue: false receivers: - name: 'oncall-page' webhook_configs: - url: '${FEISHU_WEBHOOK_URL}' send_resolved: true http_config: bearer_token: '${FEISHU_TOKEN}' email_configs: - to: '${ONCALL_EMAIL}' from: '${ALERT_FROM}' smarthost: '${SMTP_HOST}' - name: 'security-team' webhook_configs: - url: '${SECURITY_FEISHU_WEBHOOK_URL}' send_resolved: true - name: 'warning-channel' webhook_configs: - url: '${WARNING_FEISHU_WEBHOOK_URL}' send_resolved: true - name: 'default' email_configs: - to: '${ALERTMANAGER_DEFAULT_TO}' from: '${ALERTMANAGER_FROM}' smarthost: '${ALERTMANAGER_SMARTHOST}' inhibit_rules: # Critical 抑制同服务 Warning - source_match: severity: 'critical' target_match: severity: 'warning' equal: ['alertname'] ``` --- ## 六、混沌工程方案 ### 6.1 混沌工程实施路线图 ``` 第1阶段(现在):游戏日(Game Day) └── 手动故障注入 + 观察系统行为 └── 目标:发现未知故障模式 第2阶段(1个月后):脚本化故障注入 └── PowerShell/Shell 脚本 └── 目标:可重复验证 第3阶段(3个月后):持续混沌(Continuous Chaos) └── 定时自动化故障注入 └── 目标:回归防护 ``` ### 6.2 故障注入实验清单 | 实验 ID | 故障类型 | 注入方式 | 预期行为 | 验证指标 | |---------|----------|----------|----------|----------| | CE-001 | 数据库不可用 | 关闭 SQLite 文件句柄 | 返回 503,健康检查降为 DOWN | `health_check_status == DOWN` | | CE-002 | Redis 不可用 | 停止 Redis 服务 | 降级到 L1 缓存,业务继续 | 错误率无显著上升 | | CE-003 | 高内存压力 | 注入内存泄漏 goroutine | GC 正常运行,不 OOM | `system_goroutines`, 内存告警 | | CE-004 | 网络延迟 | 添加人工 sleep | P99 延迟告警触发 | `APILatency_FastBurn` 触发 | | CE-005 | 大量并发登录 | 压测工具 | 速率限制正确工作 | 登录接口 429 响应率 | | CE-006 | JWT Secret 轮换 | 更换配置重启 | 现有 token 失效优雅处理 | 401 率短暂上升后恢复 | | CE-007 | 进程崩溃恢复 | SIGKILL 进程 | 重启后状态恢复 | 服务可用性恢复时间 | | CE-008 | 暴力破解攻击 | ab/wrk 高频失败登录 | 账号锁定 + IP 封禁 | `HighLoginFailureRate_BruteForce` | ### 6.3 混沌实验脚本(CE-005:并发登录压测) ```powershell # scripts/chaos/ce-005-concurrent-login.ps1 # 目标:验证速率限制在高并发下是否正常工作 param( [string]$BaseURL = "http://localhost:8080", [int]$Concurrency = 50, [int]$Duration = 30 ) Write-Host "=== CE-005: 并发登录压测 ===" Write-Host "目标: $BaseURL" Write-Host "并发数: $Concurrency" $results = @{ total = 0 success = 0 rate_limited = 0 other_error = 0 } $jobs = 1..$Concurrency | ForEach-Object { Start-Job -ScriptBlock { param($BaseURL, $Duration) $end = (Get-Date).AddSeconds($Duration) $local_results = @{ total=0; success=0; rate_limited=0; error=0 } while ((Get-Date) -lt $end) { try { $body = @{ account = "testuser_$((Get-Random -Max 1000))" password = "wrongpassword" } | ConvertTo-Json $resp = Invoke-WebRequest -Uri "$BaseURL/api/v1/auth/login" ` -Method POST -Body $body -ContentType "application/json" ` -ErrorAction SilentlyContinue $local_results.total++ switch ($resp.StatusCode) { 200 { $local_results.success++ } 429 { $local_results.rate_limited++ } default { $local_results.error++ } } } catch { $local_results.error++ } } return $local_results } -ArgumentList $BaseURL, $Duration } $jobs | Wait-Job | ForEach-Object { $r = Receive-Job $_ $results.total += $r.total $results.success += $r.success $results.rate_limited += $r.rate_limited $results.other_error += $r.error } Write-Host "`n=== 压测结果 ===" Write-Host "总请求: $($results.total)" Write-Host "成功: $($results.success)" Write-Host "速率限制(429): $($results.rate_limited)" Write-Host "其他错误: $($results.other_error)" Write-Host "速率限制比例: $([math]::Round($results.rate_limited / [math]::Max($results.total,1) * 100, 2))%" # 验证:速率限制应该触发 if ($results.rate_limited -gt 0) { Write-Host "`n✅ 实验通过:速率限制正常工作" -ForegroundColor Green } else { Write-Host "`n❌ 实验失败:速率限制未触发,需要检查配置" -ForegroundColor Red exit 1 } ``` --- ## 七、容量规划 ### 7.1 当前资源基线 | 资源 | 当前配置 | 预估容量 | 瓶颈风险 | |------|----------|----------|----------| | 并发用户 | 未测量 | ~500(估算) | 数据库写锁(SQLite) | | 内存 | 未监控 | <500MB | 高 | | L1 Cache | 10000 条目 | ~100MB | 低 | | 速率限制 | 1000 req/min | 16.7 req/s | 取决于业务 | | DB 连接池 | 未配置(GORM 默认) | 10 并发 | 高 | ### 7.2 扩展路线图 ``` 当前状态(SQLite 单机) ↓ 迁移触发条件:并发用户 > 100 或写入 QPS > 50 PostgreSQL 单主 ↓ 扩展触发条件:读写比 > 4:1 或主库 CPU > 60% PostgreSQL 主从(读写分离) ↓ 扩展触发条件:单机不足支撑峰值 PostgreSQL 连接池(PgBouncer) + 读副本 ``` ### 7.3 数据库连接池配置建议 ```yaml # config.yaml 推荐配置(迁移 PostgreSQL 后) database: postgresql: max_open_conns: 50 # 根据 PostgreSQL max_connections 的 1/3 设置 max_idle_conns: 10 # 保持 max_open_conns 的 20% conn_max_lifetime: 1h # 防止连接泄漏 conn_max_idle_time: 5m # 回收空闲连接 ``` --- ## 八、P0 修复实施计划 ### 8.1 立即修复(本周内) #### Fix-1:接入 Prometheus 指标端点 修改 `cmd/server/main.go`,在路由中注册 `/metrics` 端点: ```go // 在 router.go 的 Setup() 函数中添加(在 v1 group 之前) import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/user-management-system/internal/monitoring" ) // Setup() 中新增 metrics := monitoring.GetGlobalMetrics() r.engine.Use(monitoring.PrometheusMiddleware(metrics)) r.engine.GET("/metrics", gin.WrapH( promhttp.HandlerFor(metrics.GetRegistry(), promhttp.HandlerOpts{ EnableOpenMetrics: true, }), )) ``` #### Fix-2:修复健康检查增加 Redis 检查 ```go // health.go 增加 Redis 检查 func (h *HealthCheck) Check() *Status { status := &Status{ Status: HealthStatusUP, Checks: make(map[string]CheckResult), } dbResult := h.checkDatabase() status.Checks["database"] = dbResult if dbResult.Status != HealthStatusUP { status.Status = HealthStatusDOWN } // 新增:Redis 检查(如果启用) if h.redisClient != nil { redisResult := h.checkRedis() status.Checks["redis"] = redisResult // Redis 不可用视为 degraded,不影响主服务状态 // 但记录为 WARN } return status } ``` #### Fix-3:修复 Webhook 服务 Enabled 配置 ```go // main.go 修复 webhookService := service.NewWebhookService(db.DB, service.WebhookServiceConfig{ Enabled: cfg.Webhook.Enabled, // 从配置读取,不再硬编码 }) ``` ### 8.2 本月完成 1. 引入结构化日志(slog)替换 log.Printf 2. 新增缺失的 Prometheus 指标(cache_hits_total 等) 3. 配置飞书 Webhook 告警通道 4. 更新 alerts.yml 为燃烧率告警 5. 执行 CE-001 ~ CE-005 混沌实验并记录结果 ### 8.3 下季度完成 1. 迁移 SQLite → PostgreSQL(生产环境必须) 2. 接入 OpenTelemetry 分布式追踪 3. 建立 SLO 仪表盘(Grafana) 4. 实施错误预算政策,纳入发布流程 --- ## 九、运维手册(Runbook) ### Runbook-01:API 可用性下降 **触发条件:** `APIAvailability_FastBurn` 告警触发 **响应步骤:** 1. 检查健康检查:`curl http://服务地址/health/ready` 2. 检查最近部署:`git log --oneline -10` 3. 检查数据库:`curl http://服务地址/health | jq .checks.database` 4. 检查错误日志:`tail -100 logs/app.log | grep "ERROR"` 5. 若数据库异常 → 执行数据库恢复流程 6. 若最近有部署 → 评估回滚:`git revert HEAD` 7. 上报状态给用户(若影响 > 5 分钟) **恢复目标:** MTTR < 30分钟 --- ### Runbook-02:疑似暴力破解 **触发条件:** `HighLoginFailureRate_BruteForce` 告警触发 **响应步骤:** 1. 查看攻击源 IP:检查登录日志 `GET /api/v1/logs/login` 2. 确认 IP 封禁已生效:查看 `anomaly_detected_total{type="brute_force"}` 3. 若 IP 封禁未生效:手动加入 IP 黑名单(ip_security 配置) 4. 通知安全团队 5. 评估是否需要临时提高速率限制阈值 --- ### Runbook-03:数据库不可用 **触发条件:** `DatabaseDown` 告警触发 **响应步骤:** 1. 立即检查:`sqlite3 data/user_management.db ".tables"` 2. 若文件损坏:执行备份恢复: ```powershell powershell -ExecutionPolicy Bypass -File scripts/ops/drill-sqlite-backup-restore.ps1 ``` 3. 若进程锁定:检查是否有孤儿进程占用文件 4. 迁移计划:SQLite 单点是已知风险,立即提升 PostgreSQL 迁移优先级 --- ## 十、SRE 度量指标(季度回顾) | 指标 | 目标 | 测量方法 | |------|------|----------| | **MTTR**(平均恢复时间) | < 30分钟 | 事件记录 | | **MTBF**(平均无故障时间) | > 720小时 | 运行日志 | | **错误预算消耗率** | < 50%/月 | Prometheus | | **告警噪声比** | < 10%(告警中非实际问题的比例) | 人工评审 | | **混沌实验通过率** | > 80% | 实验记录 | | **手册完备率** | 每个 P0 告警对应手册 | 文档检查 | --- ## 附录 A:SRE 工具链建议 | 工具 | 用途 | 当前状态 | |------|------|----------| | Prometheus | 指标采集 | ✅ 已配置(需接路由) | | Grafana | 指标可视化 | ✅ 仪表盘已有 | | Alertmanager | 告警路由 | ✅ 已配置(需真实通道) | | OpenTelemetry | 分布式追踪 | ❌ 缺失 | | 飞书/企业微信 Webhook | 即时告警 | ❌ 缺失 | | PagerDuty/oncall | On-Call 管理 | ❌ 缺失 | | k6/wrk | 压力测试 | ❌ 缺失 | | 日志聚合(Loki/ELK) | 日志查询 | ❌ 缺失 | --- ## 附录 B:快速健康检查命令 ```powershell # 系统整体健康状态 Invoke-RestMethod -Uri "http://localhost:8080/health/ready" # 检查指标端点(修复后) Invoke-RestMethod -Uri "http://localhost:8080/metrics" # 检查登录接口延迟 Measure-Command { Invoke-RestMethod -Uri "http://localhost:8080/api/v1/auth/capabilities" } # 检查速率限制 1..10 | ForEach-Object { $resp = Invoke-WebRequest -Uri "http://localhost:8080/api/v1/auth/login" ` -Method POST -Body '{"account":"x","password":"x"}' ` -ContentType "application/json" -ErrorAction SilentlyContinue Write-Host "请求 $_: HTTP $($resp.StatusCode)" } ``` --- *本报告由 SRE 工程师完成全面审查,问题分级标准参照 Google SRE Book。所有 P0 问题需在上线前修复,P1 问题需在下一个 Sprint 内修复。* *下次 SLO 回顾日期:2026-05-05*