Skip to content

缓存策略

Redis 在平台中的使用规范,包括缓存分层、Key 设计、TTL 策略和设备影子存储方案。


1. 缓存分层

Redis 在平台中承担以下角色:

分层存储内容数据结构说明
在线状态设备上下线标记Stringdevice:online:{id} = 1/0
设备影子(热缓存)设备最新一次上报的属性值Hash仅供实时读取和 WebSocket 推送,不替代 PostgreSQL 中的影子权威数据
指令状态待响应指令的临时追踪String + TTL下发指令后记录 msgId,收到 set_reply 后清除
用户会话JWT 黑名单 / 刷新令牌String登出或 Token 刷新时写入
限流计数API 请求频率计数器String + TTL配合 ThrottlerGuard 使用

2. Key 命名规范

统一命名格式:{domain}:{entity}:{id}:{field}

Key 示例说明
device:online:d1a2b3c4设备 d1a2b3c4 的在线状态
device:shadow:d1a2b3c4设备 d1a2b3c4 的影子缓存(Hash,field=属性名,value=属性值)
cmd:status:msg12345指令 msg12345 的执行状态
user:token:1001用户 1001 的刷新令牌
rate:user:1001:/api/v1/devices用户 1001 对某接口的请求计数

命名原则:

  • 全小写,冒号分隔层级
  • 不包含空格和特殊字符
  • 总长度不超过 128 字符
  • 语义从大到小排列(域 → 实体 → ID → 字段)

3. 设备影子:Redis vs PostgreSQL

这是本文档最重要的部分。当前文档中存在矛盾:04.数据模型概览 说影子放 PostgreSQL,而 01.系统架构设计 的时序图中把影子写入了 Redis。

结论:两级存储,各司其职。

存储分工

存储角色数据内容读写场景
Redis热缓存(非权威)设备最近一次上报的属性值(Hash,field=属性名,value=属性值)实时推送(WebSocket 读)、MQTT 消息更新(写)
PostgreSQL权威数据源完整的影子 JSON(desired + reported + version + updatedAt)API 查询历史影子、审计追溯(读)、控制指令后的期望状态更新(写)

同步策略

1. 设备上报属性

MQTT 消息到达 → 后端处理
  ├── Redis: HSET device:shadow:{id} temp 45.2 relay_1 1(覆盖写,低延迟)
  └── PostgreSQL: UPDATE device_shadow SET reported = merge(reported, newData), updated_at = now()

同时写入两边,Redis 先写(保证推送实时性),PostgreSQL 后写(保证数据持久化)。

2. 用户查询设备详情(API)

直接读 PostgreSQL 影子 → 返回完整 JSON(含 desired + reported + version)。不依赖 Redis,因为 Redis 可能刚过期或重启丢失。

3. WebSocket 推送

读 Redis Hash → 构造推送消息。高频、低延迟场景。Redis 不可用时降级读取 PostgreSQL(延迟略高但数据不丢失)。

4. 云端修改期望状态(控制指令)

写 PostgreSQL 影子(desired 部分)→ 通过 MQTT 下发指令 → 设备执行后上报 actual 状态 → 回到步骤 1 的同步流程。

5. Redis 故障/重启

  • 影子缓存首次访问时从 PostgreSQL 回填:HGETALL 不存在 → SELECT reported FROM device_shadow WHERE device_id = ?HSET 回填
  • 无数据丢失风险,PostgreSQL 始终持有完整影子
  • 回填期间 WebSocket 降级读 PostgreSQL(增加约 5ms 延迟,可接受)

6. TTL 设置

  • 影子缓存 TTL:30 分钟
  • 设备持续上报属性时自动续期(每次 HSET 后 EXPIRE 刷新)
  • 无上报则 30 分钟后自动过期,释放内存
  • 下次访问时从 PostgreSQL 重新回填

同步到其他文档

以下文档中的矛盾需要在后续 PR 中修正:

文档需要修改的内容
04.数据模型概览.md"存储分层"表中"设备影子"行:存储列从"PostgreSQL (JSON)"改为"PostgreSQL (JSON) + Redis (Hash 热缓存)";说明列补充"两级存储:PostgreSQL 为权威源,Redis 为实时读写缓存"
01.系统架构设计.md"数据上报"时序图相关的详细步骤:"更新 Redis 中的设备影子"改为"更新 Redis 影子缓存 + 写入 PostgreSQL 影子"

4. TTL 策略汇总

缓存类型TTL续期过期后行为
在线状态120 秒设备心跳或新消息到达时续期判定设备离线,Redis key 自动过期
设备影子30 分钟每次属性上报续期自动清理,下次访问从 PostgreSQL 回填
指令状态30 秒不续期判定指令超时,标记为失败并通知前端
用户会话7 天刷新 Token 时续期用户需重新登录
限流计数60 秒不续期计数器归零,下个窗口重新计数

5. 缓存穿透/击穿/雪崩防护

问题场景应对
缓存穿透查询不存在的设备 ID,绕过缓存直接打到 PostgreSQL对空结果也缓存短 TTL(60 秒),值标记为 NULL,下次命中直接返回
缓存击穿热点设备影子过期瞬间,大量请求同时打到 PostgreSQL分布式锁(Redis SETNX),只有一个请求去查 DB 并回填缓存,其他请求等待锁释放后读缓存
缓存雪崩大量缓存在同一时刻过期TTL 加随机偏移(±20%),避免集中失效;核心数据(在线状态)设置永不过期 + 手动清理

6. 与 EMQX 的 Redis 使用边界

EMQX 自身也会使用 Redis 作为内部缓存(如会话持久化、共享订阅状态),与后端业务 Redis 需区分:

方案说明
不同 DB 编号EMQX 使用 DB 0,后端使用 DB 1(配置简单,推荐)
不同 Key 前缀EMQX 用 emqx: 前缀,后端用 device: / user: / cmd: 等前缀(同一 DB 内互不冲突)

EMQX 侧的 Redis 配置见 09.EMQX配置与安全