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
数据结构选择决策流程
异步任务编排中的数据结构组合策略
在实际的异步任务编排系统中,单一数据结构往往无法满足所有需求。以下是一个典型的组合方案:
| 组件 | 数据结构 | 作用 |
|---|---|---|
| 任务队列 | 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。