3. Redis 核心数据结构及其适用场景

在深入热点削峰架构之前,必须牢固掌握 Redis 的五种核心数据结构。它们是构建缓存、队列、计数器、排行榜等所有上层能力的基石。理解每种结构的底层实现与适用边界,是设计高性能削峰方案的前提。

3.1 数据结构全景对比

数据结构 底层编码 最大元素数 核心操作时间复杂度 典型场景
String int / embstr / raw 512 MB GET/SET O(1) 缓存、计数器、分布式锁
Hash ziplist / hashtable 2^32 - 1 HGET/HSET O(1) 对象存储、用户属性、会话
List quicklist 2^32 - 1 LPUSH/RPOP O(1) 消息队列、时间线、栈
Set intset / hashtable 2^32 - 1 SADD/SISMEMBER O(1) 去重、标签、关注关系
Sorted Set ziplist / skiplist 2^32 - 1 ZADD O(log N) 排行榜、延时队列、限流
架构师视角: 不要只看 API 名称,要关注底层编码转换阈值。例如 Hash 在字段数小于 512 且每个字段值小于 64 字节时使用 ziplist,内存效率极高;超过阈值后自动切换为 hashtable,读写性能不变但内存开销上升。削峰场景下,内存效率直接影响单机承载能力。

3.2 String — 最基础的原子载体

String 是 Redis 最基础的结构,value 最大 512 MB。底层根据数值大小和字符串长度自动选择 int、embstr 或 raw 编码。在热点削峰中,String 常用于:

  • 热点数据缓存: 直接缓存 DB 查询结果,配合 TTL 防止缓存雪崩。
  • 计数器: INCR / DECR 原子操作,用于秒杀库存扣减、接口限流令牌。
  • 分布式锁: SET key value NX EX seconds 实现互斥锁,是削峰入口的第一道屏障。
# 秒杀库存扣减(原子操作)
DECR stock:1001
# 结果 >= 0 表示扣减成功,< 0 表示已售罄

# 分布式锁(防并发击穿)
SET lock:order:1001 uuid NX EX 10
# 返回 OK 获得锁,执行业务后 DEL 释放

3.3 Hash — 结构化对象存储

Hash 适合存储对象类型数据,如用户信息、商品详情、配置项。相比 String 序列化整个对象,Hash 支持单独读写某个字段,节省带宽且支持部分更新。

  • 用户会话: 以 sessionId 为 key,字段存储 userId、expire、role。
  • 商品属性: 字段为 price、stock、title,热点商品可单独更新库存字段。
  • 配置中心: 应用级配置以 Hash 存储,字段粒度控制热更新。
# 设置用户信息
HSET user:1001 name "张三" age 30 city "北京"

# 只获取年龄字段(避免传输整个对象)
HGET user:1001 age

# 判断字段是否存在(用于缓存穿透校验)
HEXISTS user:1001 name

3.4 List — 消息队列与时间线

List 基于 quicklist(双向链表 + ziplist 压缩)实现,支持双端操作。在削峰架构中,List 常作为轻量级消息队列使用,但需要注意其不支持消息确认机制。

  • 异步削峰队列: 生产者 LPUSH 任务,消费者 BRPOP 阻塞消费。
  • 时间线/Feed 流: LTRIM 控制列表长度,只保留最新 N 条。
  • 栈结构: LPUSH + LPOP 实现后进先出。
注意: List 作为队列时,消费者崩溃会导致消息丢失。生产环境建议使用 Redis Stream 或 Kafka 做可靠削峰。List 适合对丢失不敏感、追求极致吞吐的场景。
# 生产者:推送订单处理任务
LPUSH order:queue "{orderId:1001, userId:5001}"

# 消费者:阻塞获取任务(超时 30 秒)
BRPOP order:queue 30

3.5 Set — 去重与关系计算

Set 基于 hashtable 或 intset 实现,元素唯一且无序。在削峰场景中,Set 主要用于去重拦截和集合运算。

  • 用户去重: 秒杀活动使用 SADD 记录已参与用户,SISMEMBER 判断是否重复。
  • 标签系统: 用户标签、商品标签,利用 SINTER 做交集推荐。
  • 黑白名单: 热点 IP 或设备 ID 直接存入 Set,SISMEMBER O(1) 判断。
# 记录已秒杀用户(去重)
SADD seckill:1001:users user5001

# 判断用户是否已参与(O(1) 拦截)
SISMEMBER seckill:1001:users user5001

# 计算两个活动的共同参与用户
SINTER seckill:1001:users seckill:1002:users

3.6 Sorted Set — 排行榜与延时队列

Sorted Set 基于 skiplist + hashtable 实现,每个元素关联一个 double 类型的 score,按 score 排序。这是削峰架构中最重要的结构之一。

  • 实时排行榜: 以 score 为热度值,ZREVRANGE 获取 Top N。
  • 延时队列: 以时间戳为 score,消费者轮询 ZRANGEBYSCORE 获取到期任务。
  • 滑动窗口限流: 以时间戳为 score,ZREMRANGEBYSCORE 清理过期记录,ZCARD 统计窗口内请求数。
# 延时队列:添加 5 分钟后执行的任务
ZADD delay:order 1620000000 "order:1001"

# 消费者:获取当前时间之前的所有到期任务
ZRANGEBYSCORE delay:order -inf 1620000000

# 滑动窗口限流:记录当前请求时间戳
ZADD rate:api:user5001 1620000000 1620000000
# 移除窗口外的记录(窗口 1 秒)
ZREMRANGEBYSCORE rate:api:user5001 -inf 1619999000
# 统计窗口内请求数
ZCARD rate:api:user5001

3.7 数据结构选型决策流程

Redis 数据结构选型决策流程 业务需求分析 需要按 score 排序? 是 Sorted Set 否 需要元素唯一? 是 Set 否 需要结构化字段? 是 Hash 否 需要双端操作? 是 List 否 String
架构师建议: 在热点削峰场景中,优先使用 String 做原子计数和分布式锁,Sorted Set 做延时队列和滑动窗口限流,Hash 做热点对象的部分更新。避免在 List 中存储大量未消费消息,防止内存堆积引发 OOM。

3.8 编码转换与内存优化

Redis 会根据数据特征自动选择底层编码,理解这些阈值有助于在削峰场景中控制内存开销:

数据结构 编码类型 触发条件 内存效率
Hash ziplist 字段数 < 512 且每个字段值 < 64 字节 极高
Hash hashtable 超过上述阈值 中等
List quicklist 始终使用 高
Set intset 所有元素为整数且数量 < 512 极高
Set hashtable 超过上述阈值 中等
Sorted Set ziplist 元素数 < 128 且每个元素值 < 64 字节 极高
Sorted Set skiplist 超过上述阈值 中等
优化技巧: 在秒杀场景中,如果参与用户 ID 是连续整数,Set 会使用 intset 编码,每个元素仅占 4 字节。此时 100 万用户仅占用约 4 MB 内存,远低于 hashtable 的 40+ MB。合理利用编码特性可以显著降低 Redis 内存压力。