Redis 中的 5 种基础数据类型(String、List、Hash、Set、ZSet)在底层实现上并不单一。为了平衡内存占用与读写性能,Redis 会根据数据量的大小、元素的长度等因素,自动在多种底层数据结构(编码类型)之间进行切换。理解这些编码类型是进行 Redis 性能调优和内存优化的关键。使用 OBJECT ENCODING 命令可以直接查看某个 Key 对应的底层编码。
以下是查看和分析 Redis 数据结构底层编码的详细步骤。
1. 准备工作
在开始操作前,需要确保本地或远程已经安装并启动了 Redis 服务。
- 打开终端或命令行工具。
- 输入连接命令并登录 Redis:
redis-cli - 输入
PING命令,若返回PONG,则表示连接成功。
2. 查看 String 类型的底层编码
String 类型是 Redis 中最基础的类型,其底层编码主要根据值的长度和内容决定。
-
执行以下命令设置一个纯整数值:
SET number 10086 -
输入查看编码的命令:
OBJECT ENCODING number -
观察返回结果,结果为
int。这说明 Redis 发现该值是长整数,直接将其存储为整数类型,以节省空间并支持数值计算。 -
执行以下命令设置一个短字符串(长度小于 44 字节):
SET short "hello redis" -
输入查看编码命令:
OBJECT ENCODING short -
观察返回结果,结果为
embstr。这是一种专门用于保存短字符串的编码方式,它只要求一次内存分配,且数据连续存储,读取速度快。 -
执行以下命令设置一个长字符串(生成一个超过 44 字节的字符串):
SET long "This is a very long string designed to exceed the embstr encoding limit of 44 bytes in Redis." -
输入查看编码命令:
OBJECT ENCODING long -
观察返回结果,结果为
raw。当字符串长度超过阈值(通常是 44 字节)时,Redis 会使用动态字符串(SDS)并使用raw编码,此时需要进行两次内存分配。
3. 查看 Hash 类型的底层编码
Hash 类型在元素较少且每个元素值较小时,会使用压缩列表以节省内存;当数据量增大时,会转换为哈希表。
-
执行以下命令创建一个包含少量字段的小 Hash:
HSET user:1 name "Tom" age 25 -
输入查看编码命令:
OBJECT ENCODING user:1 -
观察返回结果,结果为
ziplist。此时 Redis 将所有数据紧挨着存储在连续内存块中,极其节省内存。 -
为了触发编码转换,需要修改配置参数或一次性大量插入数据。这里使用脚本一次性插入 513 个字段(默认阈值通常是 512):
for i in {1..513}; do redis-cli HSET big_hash field_$i value_$i; done(注:上述命令在 Bash 环境下执行,Windows 用户可使用 PowerShell 循环或手动插入足够多的数据)
-
输入查看编码命令:
OBJECT ENCODING big_hash -
观察返回结果,结果为
hashtable。当字段数量超过hash-max-ziplist-entries(默认 512)或单个元素值长度超过hash-max-ziplist-value(默认 64 字节)时,编码会转换为哈希表。哈希表的读写时间复杂度为 $O(1)$,但内存消耗会比压缩列表大。
4. 查看 List 类型的底层编码
List 类型的底层结构在不同版本的 Redis 中有所变化。在 Redis 3.2 之后,统一使用 quicklist(快速列表)。
- 执行以下命令创建一个包含少量元素的列表:
LPUSH my_list a b c - 输入查看编码命令:
OBJECT ENCODING my_list - 观察返回结果,结果为
quicklist。quicklist是ziplist和linkedlist的结合体,它将多个ziplist使用双向指针串联起来。既保证了列表中间插入元素的性能,又减少了指针的内存开销。
5. 查看 Set 类型的底层编码
Set 集合的编码分为整数集合和哈希表。
-
执行以下命令创建一个包含少量整数的集合:
SSET numbers 1 2 3(注:若提示无效命令,请使用
SADD numbers 1 2 3) -
输入查看编码命令:
OBJECT ENCODING numbers -
观察返回结果,结果为
intset。当集合中的所有元素都是整数且元素数量较少(默认小于 512 个)时,Redis 使用紧凑的整数数组存储。 -
执行以下命令向集合中添加一个非整数元素:
SADD numbers "redis" -
输入查看编码命令:
OBJECT ENCODING numbers -
观察返回结果,结果变为
hashtable。一旦引入非整数元素,或者整数数量超过阈值,编码将立即升级为哈希表,其中 Key 为集合元素,Value 为 null。
6. 查看 ZSet 类型的底层编码
有序集合(ZSet)的编码选择逻辑与 Hash 类似,取决于元素数量和成员大小。
-
执行以下命令创建一个小的有序集合:
ZADD rank 10 "player1" 20 "player2" -
输入查看编码命令:
OBJECT ENCODING rank -
观察返回结果,结果为
ziplist。此时,ZSet 将成员和分值紧挨着存储在同一个ziplist中,先按分值排序,再存储成员。 -
执行以下命令插入大量数据(超过默认阈值 128 个元素):
for i in {1..129}; do redis-cli ZADD big_rank $i "member$i"; done -
输入查看编码命令:
OBJECT ENCODING big_rank -
观察返回结果,结果为
skiplist。当元素较多或成员字符串较长时,Redis 会使用跳跃表加字典的组合结构。跳跃表负责维持排序和范围查询的高效性($O(\log N)$),字典则保证根据成员查询分值的高效性($O(1)$)。
7. 编码转换逻辑总结
为了更直观地理解 Redis 何时进行编码转换,可以参考以下决策流程。
& Value Size < 64B?} H -->|Yes| I[ziplist] H -->|No| J[hashtable] B -->|Set| K{All Integers
& Count < 512?} K -->|Yes| L[intset] K -->|No| J B -->|ZSet| M{Count < 128
& Value Size < 64B?} M -->|Yes| I M -->|No| N[skiplist + dict] B -->|List| O[quicklist]
8. 自定义编码转换阈值
如果需要针对特定业务场景调整编码转换的触发条件,可以通过配置文件或命令行修改。
- 查看当前的 Hash 类型压缩列表阈值配置:
CONFIG GET hash-max-ziplist-entries CONFIG GET hash-max-ziplist-value - 修改配置以允许更大的压缩列表:
CONFIG SET hash-max-ziplist-entries 1000 - 执行修改后,新创建的 Hash 在元素数量小于 1000 时都会使用
ziplist编码。这会增加内存使用的紧凑性,但在大数据量下可能会导致ziplist连锁更新带来的性能抖动。

暂无评论,快来抢沙发吧!