# Redis 缓存策略文档 > 版本: v0.43 | 最后更新: 2026-06-24 ## 缓存总览 | 模块 | 缓存 | 方式 | Key 格式 | TTL | 说明 | |------|:--:|------|----------|-----|------| | Agent 指标热数据 | ✅ | Sorted Set | `agent:metrics:{hostname}:{metric}` | 7 天 | 聚合后批量写 MySQL | | 流水线日志 | ✅ | pub/sub + String | `pipeline:log:{runId}` | 30 min | 实时推送 + 历史缓存 | | Dashboard 统计 | ✅ | String (JSON) | `dashboard:stats` | 30 s | ~25 次 SQL → 1 次 Redis Get | | 服务器列表 | ✅ | String (JSON) | `servers:list:{kw}:{st}:{reg}:{p}:{ps}` | 10 s | JOIN 查询 → 缓存命中 ~1ms | | 告警中心 | ❌ | — | — | — | 表小,直接查 MySQL | | K8s 资源 | ❌ | — | — | — | 走 K8s API,不经过 MySQL | ## 缓存后端 支持双后端,通过配置 `driver` 字段切换: | Driver | 场景 | 依赖 | |--------|------|------| | `redis` | Docker / 生产环境 | 外部 Redis 7 容器 | | `redka` | 二进制独立部署 | 内嵌,零依赖 | ```yaml # config.yaml redis: driver: redis # redis | redka host: aiops-redis # Docker 容器名 port: 6379 password: "" db: 0 ``` 初始化逻辑在 `backend/cmd/main.go:129`,如果 Redis 不可用则 `cache = nil`,业务自动回退到无缓存模式。 ## 缓存接口 ```go // pkg/redis/redis.go type Cache interface { Get(ctx, key) (string, error) Set(ctx, key, value, expiration) error Del(ctx, keys...) error // ... ZAdd / ZRangeByScore / Publish 等 } ``` ## 各缓存详解 ### 1. Dashboard 统计 — `dashboard:stats` **缓存位置**: `handlers/dashboard.go:GetStats` **变更频率**: 低(统计数据,30s 延迟可接受) **Key**: 全局单键 `dashboard:stats` **实现流程**: ``` 请求 → cache.Get("dashboard:stats") ├─ hit → json.Unmarshal → 返回(跳过 ~25 次 SQL) └─ miss → MySQL 聚合查询 → json.Marshal → cache.Set(30s) → 返回 ``` **写入不是异步的**:`cache.Set` 使用 `context.Background()`,缓存写失败不影响 HTTP 响应。 ```go // 读缓存 if cached, err := h.cache.Get(c.Request.Context(), cacheKey); err == nil && cached != "" { var stats models.DashboardStats if json.Unmarshal([]byte(cached), &stats) == nil { c.JSON(200, Response{Data: stats}) return } } // ... 查询逻辑 ... // 写缓存 if data, err := json.Marshal(stats); err == nil { _ = h.cache.Set(context.Background(), cacheKey, string(data), 30*time.Second) } ``` ### 2. 服务器列表 — `servers:list:*` **缓存位置**: `handlers/server.go:ListServers` **变更频率**: 低(服务器列表相对稳定,10s TTL 足够短) **Key 格式**: `servers:list:{keyword}:{status}:{region_id}:{page}:{page_size}` - 按查询参数区分,不同过滤条件独立缓存 - 写操作不主动失效(TTL 10s 自动过期) **实现流程**: ``` 请求 → cache.Get("servers:list:{params}") ├─ hit → json.Unmarshal → 返回(跳过 Preload JOIN) └─ miss → MySQL 查询 → json.Marshal → cache.Set(10s) → 返回 ``` ```go // 读缓存 if h.cache != nil { if cached, err := h.cache.Get(c.Request.Context(), cacheKey); err == nil && cached != "" { var resp models.Response if json.Unmarshal([]byte(cached), &resp) == nil { c.JSON(200, resp) return } } } // ... 查询逻辑 ... // 写缓存 if h.cache != nil { if data, err := json.Marshal(resp); err == nil { _ = h.cache.Set(context.Background(), cacheKey, string(data), 10*time.Second) } } ``` ### 3. Agent 指标热数据 **缓存位置**: `handlers/agent.go` **Key 格式**: `agent:metrics:{hostname}:{metric}` (Sorted Set) - Agent 上报数据先写 Redis Sorted Set(速度快) - 定时任务批量聚合后写入 MySQL(持久化) - TTL 7 天,历史数据以 MySQL 为准 ### 4. 流水线日志 **缓存位置**: `handlers/ws_pipeline_log.go` + `handlers/pipeline.go` - **实时推送**: Redis Pub/Sub,构建输出实时流式推送到前端 - **历史缓存**: `pipeline:log:{runId}`,TTL 30 min - 超过 30 min 的日志从 MySQL 回查 ## 容错设计 | 场景 | 行为 | |------|------| | Redis 不可用(启动时) | `cache = nil`,日志 Warn,正常启动 | | Redis 不可用(运行时) | `cache.Get()` 返回 error → 走 MySQL | | 缓存值反序列化失败 | 走 MySQL(原逻辑) | | 缓存写失败 | 静默忽略(`_ = h.cache.Set(...)`) | **关键原则:缓存是加速层,不是依赖。任何缓存故障都不影响功能正确性。** ## 缓存大小估算 | Key | 单条大小 | 数量 | 估算总量 | |-----|---------|------|---------| | `dashboard:stats` | ~3 KB | 1 | ~3 KB | | `servers:list:*` | ~5 KB/组合 | ~5-10(常用过滤组合) | ~50 KB | | `agent:metrics:*` | ~200 B/指标 | ~15 指标 × 7 主机 | ~21 KB | | `pipeline:log:*` | ~50 KB/运行 | ~5 并发 | ~250 KB | | **合计** | | | **< 500 KB** | ## 性能对比 | 端点 | 缓存前 | 缓存命中 | 加速比 | |------|--------|---------|--------| | `GET /dashboard/stats` | ~150ms (~25 SQL) | ~1ms | ~150x | | `GET /servers` | ~10ms (Preload JOIN) | ~1ms | ~10x | ## Handler 缓存字段一览 所有带缓存的 handler 都持有 `cache ezredis.Cache` 字段,由 `routes.go` 统一注入: ```go // routes.go redisCache, _ := cache.(ezredis.Cache) dashboardHandler := handlers.NewDashboardHandler(db, redisCache) serverHandler := handlers.NewServerHandler(db, cfg.JWT.Secret, redisCache) pipelineHandler := handlers.NewPipelineHandler(db, redisCache) wsLogHandler := handlers.NewWSPipelineLogHandler(db, redisCache) agentHandler := handlers.NewAgentHandler(db, redisCache, metricsHub) ```