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 数据结构。决策路径从数据是否需要排序、是否需要去重、是否需要结构化访问三个维度展开。
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 作为消息缓冲时,注意控制队列长度,避免内存溢出。