Redis数据结构详解
Redis中的数据结构
1. 五大基本数据类型和底层结构
String(字符串)—— Redis 最基础的 “万能型” 结构
- 底层实现(3 种编码自动切换):
- int:存储 64 位整数(如 123、456),直接以整数形式存储,而非字符串,节省内存 + 支持原子增减;
- embstr:存储短字符串(默认≤44 字节),将「Redis 对象头 + 字符串内容」连续存储在一块内存,减少内存碎片;
- raw:存储长字符串(>44 字节),对象头和字符串内容分开存储,避免大字符串占用连续内存。
- 核心特点:
- Key 和 Value 本质都是字符串,Value 可存储文本、数字、二进制(如图片、序列化数据),最大 512MB;
- 数字操作(INCR/DECR)是原子操作(单线程执行,无并发问题),这是计数器场景的核心优势。
- 常用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 基础操作
SET key value [EX 过期时间] [NX/XX] # NX=不存在才设,XX=存在才设
GET key # 获取值
DEL key # 删除
# 数字原子操作
INCR key # 整数+1
INCRBY key 10 # 整数+10
DECR key # 整数-1
# 字符串操作
APPEND key "abc" # 追加字符串
STRLEN key # 获取字符串长度
SUBSTR key 0 2 # 截取子串(索引0到2)- 典型场景:
- 计数器:文章阅读量、接口限流计数(INCR 原子性);
- 缓存:简单的键值缓存(如用户昵称、接口返回数据);
- 分布式锁:SET lock:order 1 NX EX 30(NX 保证互斥,EX 避免死锁)。
- 关键补充:
- int 编码的特殊情况:
- 当 Value 是可转成 64 位整数的字符串(如 “123456”),Redis 会自动转 int 编码,直接用对象头的 ptr 存数值,完全不用 SDS 的 buf 数组,内存利用率拉满;
- 只有数值超出 64 位范围,才会转 embstr/raw 编码。
- embstr 和 raw 的核心区别:
- embstr:一次性分配连续内存(对象头 + SDS + 字符串),销毁时一次性释放,效率高;但因为内存连续,无法扩容(扩容会触发转 raw);
- raw:分两块内存存储,SDS 可自由扩容(按 len*2 预留空间),适合长字符串。
- int 编码的特殊情况:
- 核心总结(补全之前的知识点)
- SDS 是 Redis 字符串的 “底层载体”:所有 String 类型(除 int 编码的整数)最终都由 SDS 存储,解决了 C 字符串的性能 / 功能缺陷;
- int/embstr/raw 是 SDS 的 “编码优化”:Redis 根据字符串内容(整数 / 长短)选择不同编码,平衡内存和性能;
- 核心逻辑:String 是 Redis 对外暴露的 “数据结构名”,SDS 是底层实现,int/embstr/raw 是基于 SDS 的编码策略 —— 三者的关系是「对外接口 → 编码优化 → 底层实现」。
- 底层实现(3 种编码自动切换):
Hash(哈希)—— 适合存储 “结构化对象”
- 底层实现(2 种编码自动切换):
- ziplist(压缩列表):Hash 元素少(默认≤512 个)且字段 / 值都短(默认≤64 字节)时使用;
- 底层是连续内存的数组,按「字段 + 值 + 字段 + 值」顺序存储,内存利用率极高;
- 缺点:查找元素需遍历,元素多了性能下降。
- hashtable(哈希表):Hash 元素多或字段 / 值长时自动切换;
- ziplist(压缩列表):Hash 元素少(默认≤512 个)且字段 / 值都短(默认≤64 字节)时使用;
- 底层是数组 + 链表(Redis 6.0+ 引入哈希表扩容时的渐进式 rehash,避免阻塞);
- 优点:查找 / 插入 / 删除 O (1),适合大哈希表;
- 缺点:内存占用比 ziplist 高(有链表指针开销)。
- 核心特点:
- 一个 Key 对应多个「字段 (Field)- 值 (Value)」对,类似 Java 的 HashMap;
- 可单独操作某个 Field,无需修改整个对象(比 String 存储 JSON 更灵活,节省带宽)。
- 常用命令:
1
2
3
4
5
6
7
8
9# 基础操作
HSET key field value # 设置单个字段
HGET key field # 获取单个字段
HGETALL key # 获取所有字段和值
HDEL key field # 删除字段
HLEN key # 获取字段数量
# 原子操作
HINCRBY key field 5 # 字段值(数字)+5- 典型场景:
- 存储对象:用户信息(user:1001 → name: 张三,age:25, phone:138xxxx);
- 购物车:cart:user:1001 → 商品 ID: 数量(如 10086:2, 10087:1)。
- 底层实现(2 种编码自动切换):
List(列表)—— 有序可重复的 “双向链表”
- 底层实现(Redis 3.2+ 统一为 quicklist):
- 早期 Redis:短列表用 ziplist,长列表用 linkedlist(双向链表);
- quicklist(快速列表):Redis 3.2+ 唯一实现,是「ziplist + linkedlist」的结合体;
- 底层是双向链表,每个链表节点是一个 ziplist;
- 既保留了 linkedlist 两端操作高效的特点,又通过 ziplist 减少内存碎片,平衡性能和内存。
- 核心特点:
- 有序、可重复,支持按索引访问;
- 两端(头 / 尾)操作(LPUSH/RPUSH/LPOP/RPOP)效率 O (1),中间插入 / 删除 O (n)(需遍历);
- 支持阻塞弹出(BRPOP/BLPOP),适合做简单的消息队列。
- 常用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13# 插入操作
LPUSH key val1 val2 # 从左侧(头)插入
RPUSH key val3 val4 # 从右侧(尾)插入
# 弹出操作
LPOP key # 左侧弹出(非阻塞)
RPOP key # 右侧弹出(非阻塞)
BRPOP key 0 # 右侧阻塞弹出(0=永久阻塞,直到有数据)
# 查询/修改
LRANGE key 0 -1 # 获取所有元素(0=-1表示全量)
LLEN key # 获取列表长度
LSET key 0 "newval" # 修改指定索引的元素- 典型场景:
- 消息队列:简单异步任务(如订单通知、日志收集);
- 最新榜单:朋友圈动态、评论列表(LPUSH 新增,LRANGE 取最新 N 条);
- 栈 / 队列:LPUSH+LPOP = 栈,LPUSH+RPOP = 队列。
- 底层实现(Redis 3.2+ 统一为 quicklist):
Set(集合)—— 无序不可重复的 “哈希表 / 整数集”
- 底层实现(2 种编码自动切换):
- intset(整数集合):Set 中所有元素都是整数且数量少(默认≤512 个)时使用;
- 底层是有序整数数组,内存利用率极高,查找用二分查找 O (logn);
- 缺点:插入需移动数组元素,数量多了性能下降。
- hashtable(哈希表):Set 包含非整数元素,或整数数量超过阈值时切换;
- 底层是哈希表,Value 固定为 null(只利用 Key 去重);
- 优点:查找 / 插入 / 删除 O (1),支持海量元素。
- intset(整数集合):Set 中所有元素都是整数且数量少(默认≤512 个)时使用;
- 核心特点:
- 元素唯一(自动去重)、无序;
- 支持高效的集合运算:交集(SINTER)、并集(SUNION)、差集(SDIFF)。
- 常用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 基础操作
SADD key val1 val2 # 添加元素(自动去重)
SMEMBERS key # 获取所有元素
SISMEMBER key val1 # 判断元素是否存在(O(1))
SREM key val1 # 删除元素
SCARD key # 获取元素数量
# 集合运算
SINTER key1 key2 # 求交集(共同元素)
SUNION key1 key2 # 求并集(所有元素)
SDIFF key1 key2 # 求差集(key1有、key2无)
# 随机操作
SRANDMEMBER key # 随机获取一个元素(不删除)
SPOP key # 随机弹出一个元素(删除)- 典型场景:
- 去重:点赞、收藏、签到(避免重复操作);
- 社交场景:共同好友、共同关注(SINTER);
- 抽奖:随机抽取中奖用户(SRANDMEMBER/SPOP)。
- 底层实现(2 种编码自动切换):
Sorted Set(ZSet)—— 带分数的 “有序集合”
- 底层实现(2 种编码自动切换):
- ziplist(压缩列表):ZSet 元素少(默认≤128 个)且成员 / 分数都短(默认≤64 字节)时使用;
- 按「成员 + 分数」顺序存储,且按分数排序,内存利用率极高;
- 缺点:查找 / 排序需遍历,元素多了性能下降。
- skiplist + dict(跳表 + 哈希表):元素多或成员 / 分数长时切换(Redis 核心实现);
- dict:存储「成员→分数」的映射,O (1) 查找成员的分数;
- skiplist(跳表):存储「分数→成员」的有序结构,按分数排序,支持范围查询 O (logn);
- 为什么不用红黑树?跳表实现更简单,范围查询更高效(红黑树范围查询需中序遍历)。
- ziplist(压缩列表):ZSet 元素少(默认≤128 个)且成员 / 分数都短(默认≤64 字节)时使用;
- 核心特点:
- 元素唯一,每个元素绑定一个浮点型 “分数(score)”,按分数有序排列;
- 支持按分数范围、排名范围查询,排序效率极高。
- 常用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 基础操作
ZADD key 95 user1 88 user2 98 user3 # 添加元素(score+成员)
ZSCORE key user1 # 获取成员的分数
ZREM key user2 # 删除成员
# 按排名查询(分数从低到高)
ZRANGE key 0 -1 WITHSCORES # 获取所有元素+分数
ZRANGE key 0 1 WITHSCORES # 获取前2名(排名从0开始)
# 按分数查询
ZRANGEBYSCORE key 80 95 WITHSCORES # 分数80-95的元素
ZCOUNT key 80 95 # 分数80-95的元素数量
# 分数增减
ZINCRBY key 2 user2 # user2分数+2(变为90)- 典型场景:
- 排行榜:游戏积分、商品销量、文章点赞数(ZREVRANGE 取前 N 名);
- 延时队列:score 设为过期时间戳,轮询 ZRANGEBYSCORE 获取到期任务;
- 权重排序:按优先级处理任务(score 越高,优先级越高)。
- 底层实现(2 种编码自动切换):
关键底层设计总结(新手必记)
- 编码自动切换:Redis 对所有复杂结构都做了 “小数据用紧凑编码(ziplist/intset)、大数据用高效编码(hashtable/skiplist)” 的优化,平衡内存和性能;
- 单线程 + 高效结构:Redis 单线程执行命令,底层用哈希表、跳表、双向链表等高效结构,保证核心操作 O (1)/O (logn),性能极高;
- 内存友好:紧凑编码(ziplist/intset)无指针开销,内存利用率比纯链表 / 哈希表高一个量级,适合小数据场景。
核心场景速记
| 数据结构 | 核心优势 | 最佳场景 |
|---|---|---|
| String | 简单、原子数字操作 | 缓存、计数器、分布式锁 |
| Hash | 结构化存储、按需修改 | 用户信息、购物车、对象缓存 |
| List | 有序、两端操作高效 | 消息队列、最新榜单、栈 / 队列 |
| Set | 去重、集合运算 | 点赞、共同好友、抽奖 |
| ZSet | 有序、分数排序 | 排行榜、延时队列、权重任务 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Chu_Yu-blog!






