Files
user-system/docs/sre/SRE_SOLUTION.md
long-agent 5b6bd93179 refactor: 整理项目根目录结构
整理内容:
- 删除 60+ 临时测试输出文件 (*.txt)
- 移动二进制文件到 bin/ 目录
- 移动 Shell 脚本到 scripts/ 目录
  - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh
  - scripts/deploy/: deploy_*.sh, simple_deploy.sh
  - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh
  - scripts/test/: test_*.sh, test_*.bat
- 移动批处理文件到 scripts/
- 移动 Python 脚本到 tools/
- 清理临时日志文件

保留根目录必要文件:
- go.mod, go.sum, go.work
- Makefile, docker-compose.yml
- .env.example, .gitignore
- README.md, AGENTS.md, DEPLOY_GUIDE.md

验证: go build ./... && go test ./... 通过
2026-04-07 18:10:36 +08:00

34 KiB
Raw Blame History

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-01Prometheus 指标端点未接入路由

问题描述: metrics.go 中定义了完整的 Prometheus 指标,但 main.gorouter.go没有注册 /metrics 端点。监控系统实际上收集不到任何数据。

// main.go 中缺失:
// engine.GET("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
// 当前 /health 只返回 {"status":"ok"},没有 Prometheus 格式指标

影响: Alertmanager 告警规则形同虚设Grafana 仪表盘无数据,所有监控告警全部失效。

修复优先级: P0 — 必须立即修复


🔴 CRIT-02PrometheusMiddleware 未挂载到路由

问题描述: monitoring/middleware.go 中定义了 PrometheusMiddleware,但 router.goSetup() 方法中没有调用HTTP 请求计数和延迟指标全部为零。

影响: HighErrorRateHighResponseTimeUnusualAPIRequestRate 三个核心告警永远不会触发。

修复优先级: P0


🔴 CRIT-03SLO 完全缺失

问题描述: 系统没有定义任何 SLO服务级别目标。没有 SLO 意味着:

  • 不知道什么样的错误率是"可接受"的
  • 错误预算无法计算,无法指导发布决策
  • 告警阈值缺乏业务依据(当前 5% 错误率阈值是拍脑袋来的)

影响: 整个可靠性工程体系缺少地基。

修复优先级: P0


🔴 CRIT-04仅邮件告警无 On-Call 升级链路

问题描述: alertmanager.yml 中只配置了 email_configs且收件人地址全是占位符 ${ALERTMANAGER_CRITICAL_TO}。生产环境:

  • 无即时通知渠道(钉钉/飞书/PagerDuty/企业微信)
  • 无 On-Call 轮班配置
  • Critical 告警和 Warning 告警都发邮件,无差异化响应

影响: 凌晨 3 点系统宕机,值班工程师无法被及时叫醒。

修复优先级: P0


🔴 CRIT-05SQLite 用于运行时(单点故障)

问题描述: 当前 config.yaml 配置为 SQLite这意味着

  • 无主从复制,无读写分离
  • 写操作串行化WAL 模式下并发受限)
  • 无法水平扩展
  • 文件级单点故障

影响: 任何磁盘故障或进程崩溃都会导致完全不可用SPOF

修复优先级: P0生产上线前必须迁移至 PostgreSQL


🟡 WARN-01L1 Cache updateAccessOrder 时间复杂度 O(n)

问题描述: l1.goupdateAccessOrder 方法使用线性扫描,时间复杂度为 O(n)。当缓存接近 10000 条目时,每次缓存读取都会触发最坏 O(10000) 遍历。

// 当前实现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.goCheck() 方法只检查数据库,没有检查 Redis 连接状态(当 L2 Cache 启用时。Redis 故障会导致缓存降级,但健康检查仍返回 UP。

修复优先级: P1


🟡 WARN-03Webhook 服务 Enabled 硬编码为 false

问题描述: main.go 中:

webhookService := service.NewWebhookService(db.DB, service.WebhookServiceConfig{
    Enabled: false,  // ← 硬编码config.yaml 中 webhook.enabled=true 被忽略
})

影响: Webhook 功能实际上完全禁用,与配置文件不一致。

修复优先级: P1


🟡 WARN-04缺少分布式追踪Tracing

问题描述: config.yamlmonitoring.tracing.enabled: false,系统完全没有链路追踪能力。当一个请求经过多个 Service 时,无法追踪请求路径。

影响: 排查跨 Service 问题时平均恢复时间MTTR会大幅增加。

修复优先级: P1


🟡 WARN-05结构化日志未完整实现

问题描述: config.yaml 定义了 JSON 格式日志,但实际代码中大量使用 log.PrintfGo 标准库),不携带 trace_id、request_id、user_id 等上下文字段。

影响: 日志无法有效聚合查询,排障困难。

修复优先级: P1


🟢 INFO-01速率限制 Map 无界增长(历史遗留)

问题描述: 历史代码审查记录中曾提及 Rate limiter map 无界限增长风险。需确认当前实现是否已修复。


三、SLO 定义与错误预算

3.1 SLO 框架

# 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 指标补强清单

当前缺失的关键指标:

// 需要新增的 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 结构化日志方案

日志字段标准:

// 每条日志必须携带的上下文字段
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/slogGo 1.21+

// 替换 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 分布式追踪接入

// 最小化追踪接入方案
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告警

# 改进后的 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 多通道告警接收配置

# 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并发登录压测

# 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 数据库连接池配置建议

# 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 端点:

// 在 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 检查

// 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 配置

// 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-01API 可用性下降

触发条件: 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 -ExecutionPolicy Bypass -File scripts/ops/drill-sqlite-backup-restore.ps1
    
  3. 若进程锁定:检查是否有孤儿进程占用文件
  4. 迁移计划SQLite 单点是已知风险,立即提升 PostgreSQL 迁移优先级

十、SRE 度量指标(季度回顾)

指标 目标 测量方法
MTTR(平均恢复时间) < 30分钟 事件记录
MTBF(平均无故障时间) > 720小时 运行日志
错误预算消耗率 < 50%/月 Prometheus
告警噪声比 < 10%(告警中非实际问题的比例) 人工评审
混沌实验通过率 > 80% 实验记录
手册完备率 每个 P0 告警对应手册 文档检查

附录 ASRE 工具链建议

工具 用途 当前状态
Prometheus 指标采集 已配置(需接路由)
Grafana 指标可视化 仪表盘已有
Alertmanager 告警路由 已配置(需真实通道)
OpenTelemetry 分布式追踪 缺失
飞书/企业微信 Webhook 即时告警 缺失
PagerDuty/oncall On-Call 管理 缺失
k6/wrk 压力测试 缺失
日志聚合Loki/ELK 日志查询 缺失

附录 B快速健康检查命令

# 系统整体健康状态
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