- 新增 frontend/public/favicon.ico (4 size: 16/32/48/64) - index.html .ico 引用调整到 .svg 前面(兼容旧浏览器) - README.md 全量更新:Kubernetes 完整命令列表 + K8s 管理亮点 - run.sh 主入口添加 k8s 别名(向后兼容) - aiops-development skill 更新部署命令和 favicon 文档
184 lines
5.7 KiB
Markdown
184 lines
5.7 KiB
Markdown
# 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)
|
||
```
|