# Redis + 实时数据架构

> 更新: 2026-05-22 — 修正 Redis key 命名和刷新间隔

## 架构

```
Agent(60s心跳) → Redis heartbeat:{id} (HSET, TTL=600s) → API 即时返回
                        ↑
            heartbeat_flush(10min) → MySQL (历史快照)
```

## 数据分布

| 数据 | Redis Key | 类型 | TTL | 用途 |
|------|-----------|------|-----|------|
| 心跳数据 | `heartbeat:{server_id}` | HSET | 600s | is_online, system_info, agent_version, last_heartbeat |
| 告警标记 | `alerts:{server_id}` | SET | 3600s | 服务器处于告警状态 |
| 告警广播 | `nexus:alerts` | Pub/Sub | — | WebSocket 跨 worker 告警推送 |
| 推送进度 | `sync:progress:{op}:{ts}` | STRING | — | 批量推送进度 |
| 名称/IP/密码/配置 | — (MySQL) | — | 永久 | 写少读多，不需要缓存 |

## 实现点

1. **连接池**: `redis.asyncio.BlockingConnectionPool` (ADR-008)，启动强校验 (ADR-009)
2. **心跳写入**: Agent `POST /api/agent/heartbeat` → `HSET heartbeat:{id}` + TTL 续期
3. **批量刷新**: heartbeat_flush 每 10min → SCAN `heartbeat:*` → MySQL `UPDATE servers`
4. **API /servers**: MySQL 查基础信息 + Redis `HGETALL heartbeat:{id}` 合并实时状态
5. **API /stats**: MySQL COUNT + Redis `EXISTS alerts:{id}` 统计告警数
6. **健康检测**: Redis `HGETALL heartbeat:{id}` 不存在或 is_online=False → 离线
7. **告警推送**: WebSocket broadcast (内存 + Redis Pub/Sub 两层, ADR-010) + Telegram
8. **告警冷却**: Redis `SETNX alerts:{id}` TTL=3600s 防重复告警
9. **Dashboard**: WebSocket 实时更新，收到 alert/recovery/system 事件时刷新统计

## Redis Key 完整规范

| Key 模式 | 类型 | TTL | 写入者 | 读取者 |
|----------|------|-----|--------|--------|
| `heartbeat:{server_id}` | HSET | 600s | agent.py | servers.py, heartbeat_flush.py |
| `alerts:{server_id}` | SET | 3600s | agent.py | servers.py (stats) |
| `nexus:alerts` | Pub/Sub | — | websocket.py | websocket.py (跨 worker) |

### heartbeat:{server_id} HSET 字段

| 字段 | 类型 | 说明 |
|------|------|------|
| is_online | string ("True"/"False") | 在线状态 |
| system_info | string (JSON) | 系统信息 (CPU/内存/磁盘/OS) |
| agent_version | string | Agent 版本号 |
| last_heartbeat | string (ISO8601) | 最后心跳时间 |
