Redis 核心数据结构回顾:String、List、Hash、Set、Sorted Set 及其适用场景

在构建异步任务编排系统时,Redis 的数据结构选择直接决定了缓存命中率、任务调度效率与系统扩展性。本章从底层实现与工程实践两个维度,系统回顾五种核心数据结构的特性、命令模式与典型适用场景,为后续的接口封装与调试打下坚实基础。

5 种核心数据结构概览

数据结构 底层编码 时间复杂度(关键操作) 最大存储量 核心特性
String int / embstr / raw O(1) 读写 512 MB 原子计数、二进制安全、可设 TTL
List quicklist(ziplist + linkedlist) O(1) 头尾操作,O(N) 索引 2^32 - 1 个元素 双向队列、阻塞弹出、消息队列
Hash ziplist / hashtable O(1) 单字段,O(N) 全字段 2^32 - 1 个字段 对象存储、部分更新、节省内存
Set intset / hashtable O(1) 增删查,O(N) 集合运算 2^32 - 1 个元素 去重、交并差运算、随机抽取
Sorted Set ziplist / skiplist + hashtable O(log N) 增删查,O(log N) 范围查询 2^32 - 1 个元素 有序、分值排序、排行榜、延时队列
工程提示: 在异步任务编排中,Sorted Set 与 List 是任务队列的核心载体;Hash 用于存储任务元数据;String 负责分布式锁与计数器;Set 用于去重与标签过滤。

String:最基础的缓存单元

String 是 Redis 中最简单的数据结构,但也是使用最频繁的。其底层编码根据值的大小与类型自动切换:整数使用 int 编码(8 字节),短字符串使用 embstr(连续内存),长字符串使用 raw(动态字符串)。

适用场景

  • 分布式锁: 利用 SET key value NX EX seconds 实现原子加锁与过期。
  • 计数器: INCR / DECR 用于限流、点赞数、任务重试次数。
  • 缓存对象: JSON 序列化后存储,适合小对象(< 10 KB)。
  • 分布式 ID: INCRBY 生成全局递增序列。
# 分布式锁示例
SET task:lock:12345 "worker-1" NX EX 30
# 计数器示例
INCR task:retry:67890
# 缓存对象示例
SET user:10086 '{"name":"alice","role":"admin"}' EX 3600

List:双向队列与消息通道

List 底层使用 quicklist 结构,是 ziplist 与 linkedlist 的混合体。在元素较少时使用连续内存的 ziplist,元素增多后自动转换为 quicklist 节点链,兼顾内存效率与操作性能。

适用场景

  • 任务队列: LPUSH + BRPOP 实现生产者-消费者模式。
  • 时间线/Feed 流: LPUSH + LTRIM 维护固定长度的最新记录。
  • 异步日志缓冲: 批量写入后由后台线程消费。
# 生产者
LPUSH task:queue:high '{"task_id":"a1","type":"email"}'
# 消费者(阻塞弹出,超时 5 秒)
BRPOP task:queue:high task:queue:low 5
避坑提醒: 不要使用 LRANGE 全量读取大 List(超过 1000 条),会导致 O(N) 阻塞。应使用 LTRIM 或分页读取。

Hash:结构化对象存储

Hash 适合存储对象属性,底层在字段数少且值小时使用 ziplist,否则切换为 hashtable。相比 String 序列化整个对象,Hash 支持对单个字段进行原子读写,节省网络带宽与内存。

适用场景

  • 任务元数据: 存储任务状态、创建时间、重试次数、回调 URL。
  • 用户会话: 存储登录态、权限、过期时间。
  • 配置中心: 按模块存储配置项,支持动态更新。
# 设置任务元数据
HSET task:meta:12345 status "pending" retry_count 0 created_at "2025-03-20T10:00:00Z"
# 更新单个字段
HSET task:meta:12345 status "running"
# 获取全部字段
HGETALL task:meta:12345

Set:去重与集合运算

Set 底层使用 intset(整数集合)或 hashtable,自动根据元素类型切换。intset 在元素全为整数且数量较少时使用,内存效率极高。

适用场景

  • 任务去重: 使用 SADD 防止重复提交。
  • 标签系统: 为任务打标签,支持 SINTER 交集查询。
  • 随机抽样: SRANDMEMBER 用于负载均衡或故障转移测试。
# 添加任务 ID 到去重集合
SADD task:dedup:email "msg_001" "msg_002"
# 判断是否已存在
SISMEMBER task:dedup:email "msg_001"
# 获取两个集合的交集(共同标签)
SINTER task:tag:urgent task:tag:email

Sorted Set:有序集合与延时队列

Sorted Set 是 Redis 中最强大的数据结构之一,底层结合 skiplist(跳跃表)与 hashtable,实现 O(log N) 的插入、删除、范围查询。每个元素关联一个 double 类型的分值,按分值排序。

适用场景

  • 延时任务队列: 以任务执行时间戳作为分值,ZRANGEBYSCORE 拉取到期任务。
  • 排行榜: 按分数排序,ZREVRANGE 获取 Top N。
  • 优先级队列: 分值代表优先级,高优先级任务先出队。
  • 滑动窗口限流: 以时间戳为分值,统计窗口内请求数。
# 添加延时任务(分值 = 执行时间戳)
ZADD task:delay 1742467200 "task_001"
ZADD task:delay 1742467800 "task_002"
# 拉取当前时间之前到期的任务
ZRANGEBYSCORE task:delay -inf 1742467500 WITHSCORES
# 删除已拉取的任务
ZREMRANGEBYSCORE task:delay -inf 1742467500

数据结构选择决策流程

Redis 数据结构选择决策流程 需要存储什么数据? 单个值 / 计数器 / 锁 / 小对象 String 有序列表 / 消息队列 List 对象属性 / 部分更新 Hash 需要去重 / 集合运算 / 随机抽取? Set 需要按分值排序 / 范围查询? Sorted Set

异步任务编排中的数据结构组合策略

在实际的异步任务编排系统中,单一数据结构往往无法满足所有需求。以下是一个典型的组合方案:

组件 数据结构 作用
任务队列 List 或 Sorted Set List 用于 FIFO 队列,Sorted Set 用于延时/优先级队列
任务元数据 Hash 存储任务状态、重试次数、回调信息
分布式锁 String 防止任务重复执行
去重集合 Set 幂等性校验,防止重复提交
计数器 String 任务重试次数、限流计数
排行榜/统计 Sorted Set 任务执行耗时排名、成功率统计
最佳实践: 在异步任务编排中,优先使用 Sorted Set 作为延时队列,使用 Hash 存储任务上下文,使用 String 做分布式锁与计数器。避免在 List 中存储大对象,建议只存储任务 ID,元数据通过 Hash 按需读取。

内存优化与编码选择

Redis 会根据数据特征自动选择底层编码,但了解这些机制有助于在数据量较大时进行手动优化:

  • ziplist 阈值: Hash、List、Sorted Set 在元素数量小于 hash-max-ziplist-entries(默认 512)且单个元素大小小于 hash-max-ziplist-value(默认 64 字节)时使用 ziplist,内存节省约 60%~80%。
  • intset 阈值: Set 在元素全为整数且数量小于 set-max-intset-entries(默认 512)时使用 intset,内存效率极高。
  • String 编码: 小于 44 字节的字符串使用 embstr(连续内存),超过后使用 raw(需两次内存分配)。
# 查看 key 的底层编码
OBJECT ENCODING task:meta:12345
# 输出示例:ziplist 或 hashtable

# 配置示例(redis.conf)
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
set-max-intset-entries 512
性能注意: 当 ziplist 或 intset 超过阈值后,Redis 会触发编码转换(如 ziplist → hashtable),这是一个 O(N) 的阻塞操作。在数据量快速增长的生产环境中,建议提前预估数据规模,必要时手动调大阈值或直接使用 hashtable。