跳转至

Redis 数据结构与选择

单元 05|难度:中级|前置:数据库|目标:根据访问模式选择数据结构,而不是背命令

学习 Redis 的重点不是背命令,而是从访问模式反推数据结构。

先问五个问题

  1. 保存的是单值、对象、队列、无序集合还是有序集合?
  2. 需要按单个成员过期吗?
  3. 需要批量读取吗?
  4. 重复写入是否应该自动去重?
  5. 数据丢失后能否重建?

String:单值和独立 TTL

SET device:last_seen:10001 1753761600 EX 90
GET device:last_seen:10001

适合:

  • 一个 key 对应一个值;
  • 每个设备需要独立 TTL;
  • 计数器;
  • 简单缓存和分布式锁的基础。

批量读取使用 MGET

MGET device:last_seen:10001 device:last_seen:10002

它的主要收益是减少客户端与 Redis 之间的网络往返;Redis 仍然需要逐个查找这些 key,时间复杂度随 key 数量增长。因此不能把“单次命令”误解为“可以无成本读取十万个对象”。

批量不等于无限量

一次 MGET 十万个大 value 会阻塞 Redis 处理其他命令,并产生巨大的网络响应和应用内存占用。Web 列表应分页,离线任务应分批。

Hash:一个对象或一组字段

HSET device:10001 status online battery 80
HGET device:10001 status
HMGET device:10001 status battery

也可以用一个 Hash 保存一组设备的简短状态:

HSET tenant:42:device_status 10001 online 10002 offline
HMGET tenant:42:device_status 10001 10002

适合:

  • 一组相关字段;
  • 需要按字段读取;
  • 希望减少大量小 key 的元数据开销。

关键限制:传统 Redis Hash 的 field 不能像独立 key 那样分别设置 TTL。因此每台设备的超时检测通常用独立 key、时间轮或有序集合,而不是只靠一个大 Hash。

Set:自动去重的无序集合

SADD tenant:42:online_devices 10001
SREM tenant:42:online_devices 10001
SCARD tenant:42:online_devices
  • SADD:把成员加入集合;已存在时不重复添加。
  • SREM:移除成员;不存在时也不会产生额外副作用。
  • SCARD:返回集合成员数量。

适合维护在线设备集合,因为重复的“上线”事件不会导致同一设备被计算两次。

但 Set 本身也没有成员级 TTL。设备突然断电时不会主动执行 SREM,所以必须配合超时检测和离线事件。

计数器:INCR / DECR

INCR tenant:42:online_count
DECR tenant:42:online_count

它们是 Redis 对数值字符串执行的原子加一和减一。

优点是查询极快、占用小;缺点是重复事件、丢失事件或错误状态迁移会使计数漂移。更可靠的做法是:

  • 只有确认状态发生 offline → online 时才增加;
  • 只有确认状态发生 online → offline 时才减少;
  • 保留可重建的状态事实;
  • 定期校准统计值。

选择速查

需求 候选结构 关键原因
每台设备独立过期 String key + TTL key 可独立过期
批量读取一页设备状态 MGET / HMGET 减少网络往返
保存设备多个状态字段 Hash 字段化读写
在线设备自动去重 Set 成员唯一
获取在线设备数 SCARD 或缓存计数 避免全量扫描
按 last_seen 找超时设备 ZSet / 时间轮 按时间排序和扫描

自测

  1. 为什么 MGET 不能代替分页?
  2. 为什么一个大 Hash 不适合直接实现每台设备的独立 TTL?
  3. 重复的上线事件对 INCRSADD 分别有什么影响?
  4. 如果 Redis 丢失,在线状态能否从设备心跳重新建立?

完成标准

  • 能从访问模式选择 String、Hash、Set 或 ZSet
  • 能解释 MGET 快在哪里、边界在哪里
  • 能解释 Hash field 与独立 TTL 的矛盾
  • 完成 Redis 在线状态实验

延伸阅读:Redis 数据类型选择

下一单元:消息队列