2. Redis 核心数据结构:String、Hash、List、Set、Sorted Set 及其适用场景
Redis 之所以能成为缓存与实时数据领域的首选,核心在于其丰富且高效的数据结构。理解这五种基础数据结构的内部编码与适用边界,是构建缓存一致性方案的前提。本章将从底层编码、操作特性、典型场景三个维度进行深度剖析。
2.1 数据结构全景概览
Redis 对外暴露的五种数据类型,底层分别对应不同的内部编码(encoding),以适应不同数据规模下的性能与内存效率。下表总结了各类型的核心特征:
| 数据类型 | 底层编码(主要) | 最大元素数 | 核心操作复杂度 | 内存效率 |
|---|---|---|---|---|
| String | int / embstr / raw | 512 MB(单值) | O(1) 读写 | 极高(直接存储) |
| Hash | ziplist / hashtable | 2^32 - 1 个 field | O(1) 单 field 操作 | 高(小对象压缩) |
| List | quicklist(ziplist + linkedlist) | 2^32 - 1 个元素 | O(1) 头尾操作 / O(N) 索引 | 中(双向链表开销) |
| Set | intset / hashtable | 2^32 - 1 个元素 | O(1) 增删查 | 中(哈希表开销) |
| Sorted Set | ziplist / skiplist + hashtable | 2^32 - 1 个元素 | O(log N) 范围查询 / O(1) 单点 | 低(双结构维护) |
面试高频点: 当 Hash/List/Sorted Set 的元素数量或单个元素大小超过阈值时,底层编码会从压缩的
ziplist 自动升级为 hashtable 或 skiplist。理解这个转换机制,能帮助你预判内存拐点。
2.2 String:最基础的缓存单元
String 是 Redis 中最简单的数据类型,但也是缓存一致性中最常用的载体。它支持文本、整数、浮点数以及二进制安全的数据(如序列化对象)。
适用场景
- 单值缓存: 用户 Session、配置项、API Token。
- 计数器: 页面 PV/UV、限流令牌桶、库存扣减(
INCR/DECR原子操作)。 - 分布式锁: 利用
SETNX+ 过期时间实现互斥锁。 - 对象缓存: 将 JSON 或 Protobuf 序列化后直接存储。
# 缓存用户信息(JSON 序列化)
SET user:1001 '{"id":1001,"name":"Alice","role":"admin"}'
# 原子自增(库存扣减)
DECR product:2001:stock
# 分布式锁
SET lock:order:3001 "thread-1" NX EX 10
缓存一致性陷阱: 使用 String 缓存对象时,若对象内部字段频繁更新,每次都需要全量序列化/反序列化,导致网络带宽和 CPU 浪费。此时应考虑使用 Hash 结构进行字段级更新。
2.3 Hash:对象级缓存的利器
Hash 允许你对一个对象内部的多个字段进行独立操作,非常适合存储结构化数据。它天然支持部分更新,无需传输整个对象。
适用场景
- 用户资料/商品详情: 每个用户/商品对应一个 key,字段为属性名。
- 购物车: 用户 ID 为 key,商品 ID 为 field,数量为 value。
- 配置中心: 应用名称为 key,配置项为 field。
# 设置用户资料(字段级)
HSET user:1001 name "Alice" age 30 role "admin"
# 只更新年龄字段
HSET user:1001 age 31
# 获取所有字段
HGETALL user:1001
2.4 List:消息队列与时间轴
List 基于双向链表实现,支持在头部和尾部快速插入/弹出。结合阻塞操作 BLPOP/BRPOP,可以构建轻量级消息队列。
适用场景
- 消息队列: 生产者
LPUSH,消费者BRPOP。 - 最新动态/时间轴: 用户发帖后
LPUSH到粉丝的 List,LRANGE分页展示。 - 数据流处理: 日志收集、事件暂存。
# 生产者:推送消息
LPUSH msg:queue "task-001"
# 消费者:阻塞获取(超时 5 秒)
BRPOP msg:queue 5
性能对比: List 的
LTRIM 命令可以裁剪列表长度,常用于固定大小的最新记录缓存(如保留最近 100 条日志),避免内存无限增长。
2.5 Set:标签系统与去重
Set 是无序且元素唯一的集合,支持高效的集合运算(交集、并集、差集)。
适用场景
- 标签/分类: 用户兴趣标签、文章分类。
- 社交关系: 共同好友(交集)、可能认识的人(差集)。
- 抽奖/去重: 已参与用户 ID 集合,防止重复中奖。
# 为用户添加兴趣标签
SADD user:1001:tags "tech" "music" "sports"
# 计算两个用户的共同标签
SINTER user:1001:tags user:1002:tags
2.6 Sorted Set:排行榜与优先级队列
Sorted Set 在 Set 的基础上为每个元素关联了一个 score(分数),元素按分数排序。它结合了 skiplist 的高效范围查询和 hashtable 的快速单点访问。
适用场景
- 实时排行榜: 游戏积分榜、热榜文章(按点赞数排序)。
- 延迟队列: 将任务执行时间戳作为 score,轮询
ZRANGEBYSCORE获取到期任务。 - 滑动窗口限流: 以时间戳为 score,统计窗口内的请求数。
# 更新玩家积分
ZADD game:leaderboard 9500 "player_1001"
# 获取 Top 10 玩家
ZREVRANGE game:leaderboard 0 9 WITHSCORES
# 获取玩家排名
ZRANK game:leaderboard "player_1001"
2.7 数据结构选择决策流程
下图展示了在实际业务中,如何根据数据访问模式快速选择最合适的 Redis 数据结构:
2.8 缓存一致性视角下的结构选择
在缓存一致性实践中,数据结构的选择直接影响更新策略的复杂度:
- String + 全量更新: 适合写少读多、数据整体变更的场景。一致性维护简单,直接覆盖即可。
- Hash + 字段级更新: 适合高频部分更新的对象(如用户积分、状态)。能有效减少并发写冲突,降低缓存与数据库不一致的窗口期。
- List/Set/Sorted Set + 增量更新: 适合追加或移除元素的场景(如消息队列、排行榜)。通过
LREM、SREM、ZREM等命令实现精确的增量同步。
最佳实践: 对于需要强一致性的缓存场景,优先使用 Hash 结构进行字段级更新,配合
HSETNX 或 Lua 脚本实现原子性操作,避免因并发导致的数据覆盖。