2. Redis 基础回顾:Redis 数据结构详解
在深入削峰与缓存一致性实战之前,必须牢固掌握 Redis 的五种核心数据结构。本章将逐一剖析 String、List、Hash、Set、Sorted Set 的底层实现、适用场景、常用命令及其时间复杂度,为后续的架构设计打下坚实基础。
2.1 数据结构全景概览
Redis 的所有数据结构都以 Key-Value 形式存储,但 Value 的类型决定了操作能力与性能特征。下表对比了五种数据结构的核心差异:
| 数据结构 | 底层编码(3.2+) | 最大元素数 | 有序性 | 唯一性 | 典型应用 |
|---|---|---|---|---|---|
| String | int / embstr / raw | 512 MB | — | — | 缓存、计数器、分布式锁 |
| List | quicklist | 2³² - 1 | 插入顺序 | 否 | 消息队列、时间线、栈 |
| Hash | ziplist / hashtable | 2³² - 1 | — | 字段唯一 | 对象存储、用户属性 |
| Set | intset / hashtable | 2³² - 1 | 否 | 是 | 去重、标签、关注关系 |
| Sorted Set | ziplist / skiplist | 2³² - 1 | Score 排序 | 成员唯一 | 排行榜、延迟队列、限流 |
编码转换提示: 当 Hash 或 Sorted Set 的元素数量较少且值较小时,Redis 使用 ziplist 以节省内存;当超过阈值(默认 512 个或 64 字节)时,自动升级为 hashtable 或 skiplist。理解这一机制有助于预估内存与性能拐点。
2.2 String — 最基础的原子载体
String 是 Redis 最基础的数据结构,可以存储字符串、整数或浮点数。其底层采用 SDS(Simple Dynamic String),具备 O(1) 长度获取、预分配空间、二进制安全等特性。
常用命令与时间复杂度
| 命令 | 功能 | 时间复杂度 | 备注 |
|---|---|---|---|
SET key value |
设置键值 | O(1) | 可附加 NX/XX/EX/PX |
GET key |
获取值 | O(1) | — |
INCR key |
原子自增 1 | O(1) | 用于计数器、限流 |
MSET k1 v1 k2 v2 |
批量设置 | O(N) | N 为键数量 |
GETSET key value |
设置并返回旧值 | O(1) | 用于轮换 token |
# 实战:分布式锁 + 计数器
> SET lock:order 1 NX EX 10
OK
> INCR order:count
(integer) 1001
> GET order:count
"1001"
2.3 List — 双向链表与消息队列
List 基于 quicklist(压缩链表 + 双向链表混合)实现,支持从两端高效插入/弹出。在削峰场景中,List 常作为 异步缓冲队列 使用。
常用命令与时间复杂度
| 命令 | 功能 | 时间复杂度 | 备注 |
|---|---|---|---|
LPUSH key value |
左端插入 | O(1) | — |
RPOP key |
右端弹出 | O(1) | 实现 FIFO 队列 |
BLPOP key timeout |
阻塞左弹出 | O(1) | 实现消费者等待 |
LRANGE key start stop |
范围获取 | O(S+N) | S 为偏移量,N 为返回数 |
LLEN key |
获取长度 | O(1) | — |
避坑:
LRANGE 在列表很长时(如 10 万+)会阻塞 Redis 主线程。生产环境应避免对大 List 执行全量范围查询,建议使用 LTRIM 定期裁剪或改用 Stream。
2.4 Hash — 对象级字段存储
Hash 适合存储结构化对象(如用户信息、商品详情),支持对单个字段进行原子读写,避免序列化整个对象的开销。
常用命令与时间复杂度
| 命令 | 功能 | 时间复杂度 | 备注 |
|---|---|---|---|
HSET key field value |
设置字段 | O(1) | — |
HGET key field |
获取字段 | O(1) | — |
HMSET key f1 v1 f2 v2 |
批量设置 | O(N) | N 为字段数 |
HGETALL key |
获取所有字段与值 | O(N) | N 为字段数,大 Hash 慎用 |
HINCRBY key field inc |
字段原子增减 | O(1) | 用于计数类字段 |
# 实战:用户积分字段原子更新
> HSET user:1001 score 500
(integer) 1
> HINCRBY user:1001 score 50
(integer) 550
> HGET user:1001 score
"550"
2.5 Set — 无序唯一集合
Set 基于 hashtable 或 intset 实现,支持高效的集合运算(交集、并集、差集),适用于标签系统、好友关系、去重场景。
常用命令与时间复杂度
| 命令 | 功能 | 时间复杂度 | 备注 |
|---|---|---|---|
SADD key member |
添加成员 | O(1) | — |
SISMEMBER key member |
判断是否存在 | O(1) | — |
SMEMBERS key |
获取所有成员 | O(N) | N 为集合大小,大集合慎用 |
SINTER key1 key2 |
交集 | O(N*M) | N 为最小集合大小 |
SPOP key count |
随机弹出 | O(1) | 用于抽奖、随机选取 |
2.6 Sorted Set — 有序排行榜核心
Sorted Set 是 Redis 最强大的数据结构之一,每个成员关联一个 score,通过 skiplist + hashtable 实现 O(log N) 的插入、删除与范围查询。在削峰场景中,它常用于 滑动窗口限流 与 延迟队列。
常用命令与时间复杂度
| 命令 | 功能 | 时间复杂度 | 备注 |
|---|---|---|---|
ZADD key score member |
添加/更新成员 | O(log N) | N 为有序集合大小 |
ZRANGE key start stop |
按排名范围获取 | O(log N + M) | M 为返回数 |
ZREVRANGE key start stop |
按排名倒序获取 | O(log N + M) | 排行榜常用 |
ZRANK key member |
获取成员排名 | O(log N) | — |
ZREMRANGEBYSCORE key min max |
按分数范围删除 | O(log N + M) | 用于滑动窗口清理 |
# 实战:滑动窗口限流(1 分钟窗口,最多 100 次)
> ZADD rate:limit:user001 1700000000 "req1"
> ZREMRANGEBYSCORE rate:limit:user001 0 1699999399
(integer) 0
> ZCARD rate:limit:user001
(integer) 1
2.7 数据结构选择决策流程
下图展示了在业务场景中如何快速选择合适的数据结构:
2.8 时间复杂度速查表
以下速查表汇总了各数据结构关键操作的时间复杂度,方便在架构设计时快速评估性能风险:
| 操作类型 | String | List | Hash | Set | Sorted Set |
|---|---|---|---|---|---|
| 单元素写入 | O(1) | O(1) | O(1) | O(1) | O(log N) |
| 单元素读取 | O(1) | O(1) | O(1) | O(1) | O(log N) |
| 批量写入 | O(N) | O(N) | O(N) | O(N) | O(N log N) |
| 范围查询 | — | O(S+N) | O(N) | — | O(log N + M) |
| 集合运算 | — | — | — | O(N*M) | O(N+M) |
架构师视角: 在削峰场景中,Sorted Set 的 O(log N) 写入是可控的,但 List 的 O(1) 两端操作更适合高吞吐的瞬时缓冲。对于需要去重与排序的限流场景,Sorted Set 是唯一选择;对于纯队列缓冲,List 或 Stream 更优。