Redis 数据结构与选择¶
单元 05|难度:中级|前置:数据库|目标:根据访问模式选择数据结构,而不是背命令
学习 Redis 的重点不是背命令,而是从访问模式反推数据结构。
先问五个问题¶
- 保存的是单值、对象、队列、无序集合还是有序集合?
- 需要按单个成员过期吗?
- 需要批量读取吗?
- 重复写入是否应该自动去重?
- 数据丢失后能否重建?
String:单值和独立 TTL¶
适合:
- 一个 key 对应一个值;
- 每个设备需要独立 TTL;
- 计数器;
- 简单缓存和分布式锁的基础。
批量读取使用 MGET:
它的主要收益是减少客户端与 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 保存一组设备的简短状态:
适合:
- 一组相关字段;
- 需要按字段读取;
- 希望减少大量小 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¶
它们是 Redis 对数值字符串执行的原子加一和减一。
优点是查询极快、占用小;缺点是重复事件、丢失事件或错误状态迁移会使计数漂移。更可靠的做法是:
- 只有确认状态发生
offline → online时才增加; - 只有确认状态发生
online → offline时才减少; - 保留可重建的状态事实;
- 定期校准统计值。
选择速查¶
| 需求 | 候选结构 | 关键原因 |
|---|---|---|
| 每台设备独立过期 | String key + TTL | key 可独立过期 |
| 批量读取一页设备状态 | MGET / HMGET | 减少网络往返 |
| 保存设备多个状态字段 | Hash | 字段化读写 |
| 在线设备自动去重 | Set | 成员唯一 |
| 获取在线设备数 | SCARD 或缓存计数 | 避免全量扫描 |
| 按 last_seen 找超时设备 | ZSet / 时间轮 | 按时间排序和扫描 |
自测¶
- 为什么
MGET不能代替分页? - 为什么一个大 Hash 不适合直接实现每台设备的独立 TTL?
- 重复的上线事件对
INCR和SADD分别有什么影响? - 如果 Redis 丢失,在线状态能否从设备心跳重新建立?
完成标准¶
- 能从访问模式选择 String、Hash、Set 或 ZSet
- 能解释 MGET 快在哪里、边界在哪里
- 能解释 Hash field 与独立 TTL 的矛盾
- 完成 Redis 在线状态实验
延伸阅读:Redis 数据类型选择
下一单元:消息队列