数据结构

Redis 的高性能很大程度上得益于其精心设计的底层数据结构。理解这些数据结构是掌握 Redis 的关键。

数据类型概览 ⭐⭐⭐⭐⭐

Redis 提供了 5 种基本数据类型和多种高级数据类型:

数据类型
底层编码
应用场景

String

int、embstr、raw

缓存、计数器、分布式锁

List

quicklist (ziplist + linkedlist)

消息队列、时间线

Hash

ziplist、hashtable

对象存储、购物车

Set

intset、hashtable

去重、共同好友

ZSet

ziplist、skiplist + hashtable

排行榜、延时队列

对象编码机制

Redis 内部使用 redisObject 结构来表示所有数据类型:

typedef struct redisObject {
    unsigned type:4;        // 数据类型(String、List、Hash等)
    unsigned encoding:4;    // 编码方式(int、embstr、raw等)
    unsigned lru:24;        // LRU 时间或 LFU 数据
    int refcount;          // 引用计数
    void *ptr;             // 指向实际数据的指针
} robj;

查看对象编码


String 类型 ⭐⭐⭐⭐⭐

底层实现

String 类型根据值的不同,有 3 种编码方式

编码
条件
说明

int

值是整数且在 long 范围内

直接存储在 redisObject 的 ptr 中

embstr

字符串长度 ≤ 44 字节

redisObject 和 SDS 连续分配,只需一次内存分配

raw

字符串长度 > 44 字节

redisObject 和 SDS 分开分配

SDS(Simple Dynamic String)

Redis 自己实现的字符串结构,而不是使用 C 语言的 char*

SDS vs C 字符串

特性
C 字符串
SDS

获取长度

O(n) 遍历

O(1) 直接读取 len

二进制安全

否(遇到 \0 结束)

是(通过 len 判断)

缓冲区溢出

可能溢出

自动扩容,杜绝溢出

内存分配

每次修改都重新分配

空间预分配、惰性释放

空间预分配策略

常用命令

应用场景

1. 缓存对象

2. 分布式锁

3. 计数器


List 类型 ⭐⭐⭐⭐

底层实现:QuickList

Redis 3.2 之前使用 ziplistlinkedlist,3.2 之后统一使用 quicklist(ziplist 和 linkedlist 的混合体)。

QuickList 结构

优点

  • 每个 ziplist 存储多个元素,减少内存碎片

  • ziplist 内部连续存储,缓存友好

  • linkedlist 提供灵活的插入删除

配置

常用命令

应用场景

1. 消息队列

2. 最新动态(时间线)

3. 固定长度列表


Hash 类型 ⭐⭐⭐⭐⭐

底层实现

Hash 类型有 2 种编码方式

编码
条件
说明

ziplist

元素个数 ≤ 512 且所有值 ≤ 64 字节

紧凑存储,节省内存

hashtable

超过阈值

标准哈希表,O(1) 查找

配置

ziplist 编码示例

常用命令

应用场景

1. 对象存储

2. 购物车

3. 统计信息


Set 类型 ⭐⭐⭐⭐

底层实现

Set 类型有 2 种编码方式

编码
条件
说明

intset

所有元素都是整数且个数 ≤ 512

整数集合,紧凑存储

hashtable

超过阈值或包含非整数

哈希表,value 为 NULL

配置

intset 结构

常用命令

应用场景

1. 去重

2. 共同好友

3. 抽奖系统


ZSet 类型 ⭐⭐⭐⭐⭐

底层实现

ZSet(有序集合)有 2 种编码方式

编码
条件
说明

ziplist

元素个数 ≤ 128 且所有值 ≤ 64 字节

紧凑存储

skiplist + hashtable

超过阈值

跳表 + 哈希表

配置

跳表(Skip List)⭐⭐⭐⭐⭐

跳表是 ZSet 的核心数据结构,支持 O(log N) 查找、插入、删除。

跳表结构示例

为什么用跳表而不是红黑树?

  1. 实现简单:跳表比红黑树容易实现和调试

  2. 范围查询友好:跳表的有序链表结构更适合 ZRANGE

  3. 内存局部性:跳表的层级遍历对缓存友好

跳表 + 哈希表

  • 跳表:按 score 排序,支持范围查询

  • 哈希表:member -> score 映射,O(1) 查找

常用命令

应用场景

1. 排行榜

2. 延时队列

3. 自动补全(前缀搜索)

4. 时间范围查询


高级数据类型

HyperLogLog ⭐⭐⭐

用途:基数统计(去重计数),适合大数据量的 UV 统计。

特点

  • 误差率约 0.81%

  • 只需 12KB 内存(无论统计多少元素)

Bitmap ⭐⭐⭐⭐

用途:位图,适合二值状态统计(签到、在线状态)。

Geospatial ⭐⭐⭐

用途:地理位置信息,基于 ZSet 实现。

Stream ⭐⭐⭐

用途:消息队列(Redis 5.0+),比 List 更强大。


面试要点 ⭐⭐⭐⭐⭐

高频问题

Q1: Redis 有哪些数据类型?

  • 5 种基本类型:String、List、Hash、Set、ZSet

  • 高级类型:HyperLogLog、Bitmap、Geo、Stream

Q2: String 的底层编码有哪几种?

  • int:整数

  • embstr:短字符串(≤ 44 字节)

  • raw:长字符串(> 44 字节)

Q3: 为什么要用 SDS 而不是 C 字符串?

  • O(1) 获取长度

  • 二进制安全

  • 杜绝缓冲区溢出

  • 减少内存分配次数(空间预分配、惰性释放)

Q4: ZSet 为什么用跳表而不是红黑树?

  • 实现简单

  • 范围查询友好

  • 内存局部性好

Q5: Hash 什么时候会从 ziplist 转为 hashtable?

  • 元素个数 > 512(hash-max-ziplist-entries)

  • 单个值大小 > 64 字节(hash-max-ziplist-value)

Q6: List 的 quicklist 是什么?

  • 双向链表 + 压缩列表的混合结构

  • 每个节点是一个 ziplist,减少内存碎片

  • 平衡了内存和性能

Q7: 如何实现分布式锁?

常见误区

误区1:以为 Redis 只能存储字符串

  • 真相:Redis 支持多种数据类型,每种类型有不同的底层实现

误区2:认为 Hash 比 String 更占内存

  • 真相:Hash 使用 ziplist 编码时比 String 更节省内存

误区3:以为 ZSet 只能用于排行榜

  • 真相:ZSet 还可用于延时队列、时间范围查询等场景


参考资料

  1. 书籍推荐

    • 《Redis 设计与实现》(黄健宏)

    • 《Redis 深度历险》(钱文品)

Last updated