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 数据结构:

Redis 数据结构选择决策树 数据访问模式 单值 / 计数器 结构化对象 String Hash Hash String(JSON) 集合运算 / 去重 排序 / 范围查询 Set Sorted Set 决策要点:先判断数据是单值、对象、列表、无序集合还是有序集合,再选择对应结构

2.8 缓存一致性视角下的结构选择

在缓存一致性实践中,数据结构的选择直接影响更新策略的复杂度:

  • String + 全量更新: 适合写少读多、数据整体变更的场景。一致性维护简单,直接覆盖即可。
  • Hash + 字段级更新: 适合高频部分更新的对象(如用户积分、状态)。能有效减少并发写冲突,降低缓存与数据库不一致的窗口期。
  • List/Set/Sorted Set + 增量更新: 适合追加或移除元素的场景(如消息队列、排行榜)。通过 LREM、SREM、ZREM 等命令实现精确的增量同步。
最佳实践: 对于需要强一致性的缓存场景,优先使用 Hash 结构进行字段级更新,配合 HSETNX 或 Lua 脚本实现原子性操作,避免因并发导致的数据覆盖。