2. Redis 基础回顾:Redis 核心数据结构及适用场景

在深入削峰与连接池调优之前,必须重新审视 Redis 的五种核心数据结构。它们是所有高并发业务场景的基石。理解每种结构的底层实现、时间复杂度以及典型业务模型,是后续进行水位管理和流量控制的前提。

2.1 数据结构全景对比

下表从编码方式、时间复杂度、最大容量和核心特性四个维度,对五种数据结构进行横向对比,帮助你在设计削峰方案时快速选型。

数据结构 底层编码(3.2+) 关键操作时间复杂度 单 Key 最大容量 核心特性
String int / embstr / raw GET/SET: O(1) 512 MB 最基础;支持原子计数、位图、分布式锁
List quicklist(ziplist + linkedlist) LPUSH/RPOP: O(1);LINDEX: O(N) 2^32 - 1 个元素 双向链表;适合消息队列、时间线、栈
Hash ziplist / hashtable HGET/HSET: O(1);HGETALL: O(N) 2^32 - 1 个 field-value 对 对象结构化存储;节省内存(ziplist 模式)
Set intset / hashtable SADD/SISMEMBER: O(1);SMEMBERS: O(N) 2^32 - 1 个元素 无序唯一;支持交并差运算
Zset ziplist / skiplist + hashtable ZADD: O(log N);ZRANGE: O(log N + M) 2^32 - 1 个元素 有序唯一;按 score 排序;适合排行榜、延时队列
选型提示: 在削峰场景中,String 常用于令牌桶计数和限流计数器;List 用于异步削峰的消息缓冲;Zset 用于滑动窗口和延时任务调度。理解这些基础结构的特性,是后续章节配置连接池参数的前提。

2.2 五种数据结构详解与业务场景

2.2.1 String — 最通用的原子载体

String 是 Redis 最基础的数据类型,可以存储字符串、整数或浮点数。其原子性操作(INCR/DECR)是限流和计数器的核心实现方式。

  • 底层编码: 当值为整数且小于 2^63 时使用 int;字符串长度 ≤ 44 字节使用 embstr(只分配一次内存);超过 44 字节使用 raw(分配两次内存)。
  • 典型场景: 分布式锁(SETNX + 过期时间)、令牌桶计数(INCR + EXPIRE)、缓存热点数据(JSON 序列化后存储)、位图统计(SETBIT/GETBIT)。
# 限流计数器:每秒最多 100 次请求
INCR rate:limiter:user:123
EXPIRE rate:limiter:user:123 1
# 如果返回值 > 100,则触发限流

2.2.2 List — 异步削峰的消息缓冲

List 是一个双向链表,支持从两端高效插入和弹出。在削峰场景中,它常被用作生产者-消费者模式的消息队列,将突发的请求压力转化为异步处理。

  • 底层编码: Redis 3.2+ 使用 quicklist,它是 ziplist 和 linkedlist 的混合体,兼顾内存效率和操作性能。
  • 典型场景: 秒杀请求排队(LPUSH + BRPOP)、异步日志收集、任务队列(LPUSH + BRPOPLPUSH 实现可靠队列)。
避坑指南: 不要使用 LINDEX 或 LRANGE 对长列表进行频繁随机访问(O(N) 复杂度)。在削峰场景中,List 只应作为 FIFO 队列使用,避免在中间位置插入或查询。

2.2.3 Hash — 对象级结构化存储

Hash 允许你在一个 Key 下存储多个 field-value 对,非常适合表示对象或实体。相比将整个对象序列化为 String,Hash 支持单独读写某个字段,节省带宽和内存。

  • 底层编码: 当 field-value 对数量较少且每个 value 长度较短时使用 ziplist(内存紧凑);超过阈值后转换为 hashtable(O(1) 读写)。
  • 典型场景: 用户会话信息(HMSET user:123 name "张三" age 30)、商品详情缓存、配置中心(HGETALL 加载全量配置)。
# 存储用户信息
HSET user:1001 name "Alice" age 28 city "Beijing"
# 只获取年龄字段
HGET user:1001 age
# 输出: "28"

2.2.4 Set — 去重与集合运算

Set 是一个无序的字符串集合,自动去重。其强大的交、并、差运算能力,使其在标签系统、社交关系和去重场景中不可替代。

  • 底层编码: 当所有元素都是整数且数量较少时使用 intset(有序数组,二分查找);否则使用 hashtable。
  • 典型场景: 用户标签系统(SADD user:tags "vip" "new_user")、抽奖去重(SADD lottery:20241001 user_id)、共同好友(SINTER user:1:friends user:2:friends)。

2.2.5 Zset — 有序排行榜与滑动窗口

Zset(Sorted Set)在 Set 的基础上为每个元素关联一个 double 类型的 score,按 score 排序。它是实现延时队列、滑动窗口限流和排行榜的首选数据结构。

  • 底层编码: 使用 skiplist(跳表)作为主索引结构,配合 hashtable 实现 O(1) 的元素查找。跳表支持 O(log N) 的插入、删除和范围查询。
  • 典型场景: 实时排行榜(ZINCRBY game:score user1 10)、延时任务调度(ZADD delay:queue task1 1700000000 + ZRANGEBYSCORE 获取到期任务)、滑动窗口限流(ZREMRANGEBYSCORE 移除过期时间戳)。
# 滑动窗口限流:记录用户请求时间戳
ZADD rate:sliding:user:456 1700000000 1700000000
# 移除窗口外的记录(窗口大小 1 秒)
ZREMRANGEBYSCORE rate:sliding:user:456 0 1699999999
# 统计当前窗口请求数
ZCARD rate:sliding:user:456

2.3 数据结构选型决策流程

下面的 SVG 图展示了在业务削峰场景中,如何根据业务需求快速选择最合适的 Redis 数据结构。决策路径从数据是否需要排序、是否需要去重、是否需要结构化访问三个维度展开。

Redis 数据结构选型决策树(削峰场景) 业务需求 是否需要按 score 排序? Zset(有序集合) 排行榜、延时队列、滑动窗口 是否需要元素唯一(去重)? Set(集合) 标签系统、抽奖去重、共同好友 是否需要结构化字段访问? Hash(哈希) 用户信息、商品详情、配置 String / List 缓存/计数(String)或队列(List)

2.4 内存与性能权衡

在高并发削峰场景中,内存效率和操作延迟同样重要。下表总结了五种数据结构在内存占用和性能方面的关键权衡点,帮助你在资源受限的环境下做出合理选择。

数据结构 内存效率 读写性能 适用数据量级 注意事项
String 高(直接存储,无额外元数据) 极高(O(1)) 百万级 Key 大 Value 会触发 raw 编码,增加内存碎片
List 中(quicklist 节点有指针开销) 两端高,中间低 千万级元素 避免长列表的 LINDEX 操作
Hash 高(ziplist 模式极省内存) 高(O(1) field 访问) 百万级 field HGETALL 在 field 多时阻塞
Set 中(hashtable 有额外指针) 高(O(1) 增删查) 百万级元素 SMEMBERS 返回全量,慎用于大集合
Zset 低(skiplist + hashtable 双结构) 中(O(log N) 写入) 十万级元素 ZRANGE 带 WITHSCORES 时内存拷贝大
核心原则: 在削峰场景中,优先选择 String 和 Hash 作为缓存和计数载体,因为它们的内存效率和读写性能最高。Zset 虽然功能强大,但内存开销较大,仅在需要排序或滑动窗口时使用。List 作为消息缓冲时,注意控制队列长度,避免内存溢出。

2.5 数据结构与连接池水位的关联