Redis 核心数据结构(四)

在 Redis 核心数据结构(三) 中,重点介绍了一下 Redis 7+ 使用的底层的数据结构 listpack。本文重点看一下,Redis 是如何基于 listpack 以及其他数据结构类型来构建对外暴露的五个核心数据结构的。
quicklist
关于 quicklist 更详细的介绍,请看 Redis 核心数据结构(一:quicklist。
与上述内容不一样的地方是,现在的 quicklist 底层是使用 listpack 来构建的,而不是上述内容介绍的 ziplist。
list
关于 list-max-listpack-size 的解释,在源码中找到了详细介绍:
redis.conf# Lists are also encoded in a special way to save a lot of space.
# The number of entries allowed per internal list node can be specified
# as a fixed maximum size or a maximum number of elements.
# For a fixed maximum size, use -5 through -1, meaning:
# -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-listpack-size -2list-max-listpack-size 默认 -2 8 Kb;也可以设置成 -1, 4 Kb,也是推荐的值。没想到这个值竟然会影响到 list 中 listpack 到 quicklist 的转换条件。
$ redis-cli --raw
127.0.0.1:6379> RPUSH names diguage "D瓜哥" "https://www.diguage.com/"
3
127.0.0.1:6379> LRANGE names 0 -1
diguage
D瓜哥
https://www.diguage.com/
127.0.0.1:6379> TYPE names
list
127.0.0.1:6379> OBJECT encoding names
listpack
#...
# 加到 800 多个元素后,list 的编码方式终于变成了 quicklist
127.0.0.1:6379> RPUSH names 1234567890 ...
836
127.0.0.1:6379> OBJECT encoding names
quicklistt_list.c// server.list_max_listpack_size 应该就是上面 list-max-listpack-size
// D瓜哥添加了 800 多个元素才发生了转变,每个元素平均大概是 10 个字符,大小可以对得上。
if (quicklistNodeExceedsLimit(server.list_max_listpack_size,
lpBytes(o->ptr) + add_bytes, lpLength(o->ptr) + add_length))
{
/* Invoke callback before conversion. */
if (fn) fn(data);
quicklist *ql = quicklistNew(server.list_max_listpack_size, server.list_compress_depth);
/* Append listpack to quicklist if it's not empty, otherwise release it. */
if (lpLength(o->ptr))
quicklistAppendListpack(ql, o->ptr);
else
lpFree(o->ptr);
o->ptr = ql;
o->encoding = OBJ_ENCODING_QUICKLIST;
}根据测试和上述代码可以推断:在 list 中元素大小达到 8 Kb 时,编码就会从 listpack 转变为 quicklist。
| 📢:这点和旧版的 Redis 不太一样:旧版 Redis 直接使用 quicklist 来存储元素。而 Redis 7+,小批量数据使用 listpack 存,达到一定条件后,才开始使用 quicklist 来存数据。相比而言,Redis 7+ 内存利用率更高,效率也更好! |
set
在集合 set 中,存在三种编码方式,转变条件有对应的配置项:
redis.conf# 元素数量小于 128
set-max-listpack-entries 128
# 所有元素长度小于 64
set-max-listpack-value 64下面我们开始验证:
$ redis-cli --raw
127.0.0.1:6379> SADD numbers 1 2 3
3
127.0.0.1:6379> SMEMBERS numbers
1
2
3
127.0.0.1:6379> TYPE numbers
set
127.0.0.1:6379> OBJECT encoding numbers
intset
127.0.0.1:6379> SADD numbers "https://www.diguage.com"
1
127.0.0.1:6379> SMEMBERS numbers
1
2
3
https://www.diguage.com
127.0.0.1:6379> TYPE numbers
set
127.0.0.1:6379> OBJECT encoding numbers
listpack
127.0.0.1:6379> SADD numbers "1234567890123456789012345678901234567890123456789012345678901234"
1
127.0.0.1:6379> OBJECT encoding numbers
listpack
# 📢 注意:当添加元素长度大于 64,那么编码就从 listpack 转变成 hashtable
127.0.0.1:6379> SADD numbers "12345678901234567890123456789012345678901234567890123456789012345"
1
127.0.0.1:6379> OBJECT encoding numbers
hashtableRedis 的集合(sets)的底层存储可以使用 intset、 listpack 和 hashtable。
当集合(sets)可以同时满足以下两个条件时,集合(sets)使用 intset 编码。
集合(sets)保存的所有值都是整数,而且数字范围在 -264 ~ 264-1 之间。
集合(sets)保存的元素数量小于 128 个,(通过
set-max-intset-entries参数调节,默认是 128)。
当元素有字符串时,就会从 intset 转换成 listpack。使用 listpack 编码必须满足下面两个条件:
集合(sets)保存的元素数量小于 128 个,(通过
set-max-intset-entries参数调节,默认是 128)。集合(sets)保存的所有元素长度小于 64 个字节,(通过
set-max-listpack-value参数条件,默认是 64)。
当不满足要求时,则从 listpack 转变为 hashtable。
| 📢注意:set 的存储也和旧版发生了一下变化:旧版的 Redis 使用 intset 存数字,当有字符串时,直接使用 hashtable 来存储;而 Redis 7+ 开始,当存在字符串时,先使用 listpack 来存元素,达到一定条件后,才会切换为 hashtable。相比而言,Redis 7+ 内存利用率更高!查询效率上,可能会略差。 |
hash
hash 的编码也有两种方案,转换条件也有对应的配置项:
redis.conf# 元素数量小于 512
hash-max-listpack-entries 512
# 所有元素长度小于 64
hash-max-listpack-value 64开始验证:
$ redis-cli --raw
127.0.0.1:6379> HMSET profile name "D瓜哥" site "https://www.diguage.com" job "Developer"
OK
127.0.0.1:6379> TYPE profile
hash
127.0.0.1:6379> OBJECT encoding profile
listpack
127.0.0.1:6379> HSET profile address "1234567890123456789012345678901234567890123456789012345678901234"
1
127.0.0.1:6379> HVALS profile
D瓜哥
https://www.diguage.com
Developer
1234567890123456789012345678901234567890123456789012345678901234
127.0.0.1:6379> OBJECT encoding profile
listpack
# 📢 注意:下面编码格式开始发生变化了!
127.0.0.1:6379> HSET profile address2 "12345678901234567890123456789012345678901234567890123456789012345"
1
127.0.0.1:6379> HVALS profile
Developer
D瓜哥
1234567890123456789012345678901234567890123456789012345678901234
https://www.diguage.com
12345678901234567890123456789012345678901234567890123456789012345
127.0.0.1:6379> OBJECT encoding profile
hashtableRedis 的散列(hash)的底层存储可以使用 listpack 和 hashtable。当散列(hash)可以同时满足以下两个条件时,散列(hash)使用 listpack 编码。
散列(hash)保存的所有键值对的键和值的字符串长度都小于 64 字节。(通过参数
hash-max-listpack-value来调节,默认是 64)散列(hash)保存的键值对数量小于 512 个。(通过参数
hash-max-listpack-entries来调节,默认是 512)
zset
zset 的编码也有两种方案,转换条件也有对应的配置项:
redis.conf# 元素数量小于 128
zset-max-listpack-entries 128
# 所有元素长度小于 64
zset-max-listpack-value 64$ redis-cli --raw
127.0.0.1:6379> ZADD NameRanking 1 "D瓜哥"
1
127.0.0.1:6379> ZADD NameRanking 2 "https://www.diguage.com"
1
127.0.0.1:6379> ZADD NameRanking 3 "https://github.com/diguage"
1
127.0.0.1:6379> ZRANGE NameRanking 0 -1 WITHSCORES
D瓜哥
1
https://www.diguage.com
2
https://github.com/diguage
3
127.0.0.1:6379> TYPE NameRanking
zset
127.0.0.1:6379> OBJECT encoding NameRanking
listpack
127.0.0.1:6379> ZADD NameRanking 4 "1234567890123456789012345678901234567890123456789012345678901234"
1
127.0.0.1:6379> ZRANGE NameRanking 0 -1 WITHSCORES
D瓜哥
1
https://www.diguage.com
2
https://github.com/diguage
3
1234567890123456789012345678901234567890123456789012345678901234
4
127.0.0.1:6379> OBJECT encoding NameRanking
listpack
# 📢 注意:下面编码格式开始发生变化了!
127.0.0.1:6379> ZADD NameRanking 5 "12345678901234567890123456789012345678901234567890123456789012345"
1
127.0.0.1:6379> ZRANGE NameRanking 0 -1 WITHSCORES
D瓜哥
1
https://www.diguage.com
2
https://github.com/diguage
3
1234567890123456789012345678901234567890123456789012345678901234
4
12345678901234567890123456789012345678901234567890123456789012345
5
127.0.0.1:6379> OBJECT encoding NameRanking
skiplist
127.0.0.1:6379> TYPE NameRanking
zsetRedis 的 zset 的底层存储可以使用 listpack 和 skiplist。当 zset 可以同时满足以下两个条件时,zset 使用 listpack 编码。
有序集合保存的元素数量小于 128 个;(通过参数
zset-max-listpack-entries来调节,默认为 128。)有序集合保存的所有元素成员的长度都要小于 64 个字节;(通过参数
zset-max-listpack-value来调节,默认为 64。)
关于 skiplist 的详细介绍,请看: Redis 核心数据结构(二):skiplist。
总结
Redis 为了节省内存资源,在元素数量较少的情况下,尽量使用比较节约内存的数据结构,大部分的数据结构是从 listpack 开始的(set 在只有数字的情况下,是从 intset 开始)。在元素达到一定条件时,才会转化成比较复杂的数据结构。



