「这是我参与 2022 首次更文挑战的第 2 天,活动详情查看:2022 首次更文挑战」
一年之计在于春,一天之计在于晨。
前言
之前已经讲述了 redis 的配置文件,我们经常用到的 redis 的数据结构,但是有时候在面试的时候会被问到 redis 的编码方式,今天结合 redis 的编码方式内容和其配置进行分享。
注:Redis3.2 对列表数据结构进行了优化,新增了 quicklist 取代了 linkedlist 和 ziplist。查询 redis 的数据结构和编码方式如下:
# 查询String key的长度
strlen key_name
# 查询编码方式
object encoding key_name
redis-string 编码方式
string 数据结构的编码方式为 int、raw 和 embstr。
- 1 字符串保存的值可以转换为整数,那么该字符串对象的编码方式为 int。
- 2 如果字符串的对象是一个非整数,而且字符串的长度大于 39,那么字符串将使用一个简单动态字符传来保存这个值,其编码方式为 raw。
- 3 如果字符串的对象是一个非整数,且字符串的长度小于 39,那么字符串的编码方式为 embstr。此外,如果字符串对象的值进行了修改,那么不论其大小多少,都会转换成 embstr 编码方式。
大家会很好奇这里为什么是 39 呢?这是 redis 源代码中 REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
定义的,这个值的来源是:
string 数据结构的编码方式 embstr 和 raw 都使用 redisObject 结构和 sdshdr 结构进行表示,redis 使用 jemalloc 进行内存分配,可以分配 8、16、32、64 字节的内存
, 但是 redisObject 的需要占用 16 字节,sdshdr(也简称为 SDS,简单动态字符串) 需要占用 8 个字节, 在加上 redis 独有的 /0
分割符占用 1 个字节,那么申请 64 个字节空间时,剩余能存储的空间为 64 - 16 - 8 -1 = 39
,后来 redis 对 sdshdr 进行了结构优化(Java 中也有内存布局的概念,称之为 JOL,如果开启了指针压缩, ),使得占用空间压缩了 5 字节(8->3 ), 所以 redis3 后续版本的 REDIS_ENCODING_EMBSTR_SIZE_LIMIT 44
就是 44 个字节的限制了。
RedisObject 的结构就是元数据和指针 ptr,ptr 指向 SDS,而 SDS 的结构包括一个 buf 数组,即存储对象的字符数组,len 为数组的使用长度,alloc 为数组的长度。
这里需要说的是,redis 在使用 raw 的编码方式时,申请两块内存区域,一部分存放 redisobject 另一部分存放 SDS, 而 embstr 申请内存是一块连续的空间,因此 embstr 的查找效率相对于 raw 要高一点儿,在内存释放时 embstr 只需要释放一次,而 raw 则需要释放两次。这里可以延伸一点儿,在 mysql 数据库存储中,最小的存储单元为页(标准大小为 16kb),一行行的数据都是放在数据页中,当某些数据比较大的时候,比如字段为 text 或者 longtext 甚至是 blob 的时候,一页就放不下了,这个时候就需要在开辟一个数据页来单独存放,然后原数据页通过指针指向新的数据页。
redis-list 编码方式
redis 中的 list 数据结构在评论列表、好友列表、队列任务以及消息队列中都有重要的使用场景。list 的编码方式为 ziplist 和 linkedlist,在数据量小的时候采用压缩列表,数据量大的时候使用 linkedlist,3.2 以后就是用 quicklist 了。一般来说,redis 只要满足以下任何一个条件都会将编码方式从 ziplist 改为 quicklist。
- 1 list 对象保存的所有字符串元素的大小任何一个大于 64 kb
- 2 list 对象保存的元素数量大于 512 个
但是从配置文件来看,默认的配置是 8kb 即可转换,另外配置文件中没有找到列表对象个数的限制,这个后续看看源代码再来说吧,实践检验一下。
# 默认的大小为 8kb
list-max-ziplist-size -2
# 压缩的层级默认为 0,即都不压缩
list-compress-depth 0
redis-hash 编码方式
redis 中的 hash 结构是十分适合存储对象的,key-value 的结构和 HashMap 是比较相似的,在项目实际开发中应用也十分的广泛。和列表类似的,当 hash 对象比较小的时候采用 ziplist,比较大的时候采用 hashtable 编码方式。
- 1 hash 对象保存的任何键值对的键和值的字符串长度大于 64 个字节
- 2 hash 对象保存的键值对数量大于 512 个 对于 hash 的配置,找到了相关的佐证,当键值对的大小或者个数任何一个满足阈值即可改用 hashtable 的编码方式。
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
redis-set 编码方式
redis 中的 set 数据结构和 java 中的 Set 是类似的,存储不重复的数据集合,其编码方式分为 intset 和 hashtable。当元素均为整数且元素个数不超过 512 即使用 intset,否则都要使用 hashtable。
# 默认的个数为 512个
set-max-intset-entries 512
redis-zset 编码方式
redis 中的有序集合在应用中比较少见,常见的介绍都是用于计算分值,评分使用,其带 score 的特性也可以用来作为延迟队列使用,其编码方式也是两种 ziplist 和 skiplist。 当满足以下任何一个条件时,即可更换编码方式为 ziplist。
- 1 有序集合内的元素个数大于 128 个
- 2 有序集合内的任何一个元素代销超过 64 字节。
同时满足一下两个条件使用 ziplist,否则使用 skiplist
# 默认的ziplist 阈值个数大小为 128
zset-max-ziplist-entries 128
# 单个元素的大小为 64 kb
zset-max-ziplist-value 64
redis quicklist 的结构
如下图所示,我们可以知道 ziplist 和 linkedlist 的数据结构,在内存中如果使用双向链表是比较消耗内存的,因此在数据量小的时候采用压缩列表,数据量大的时候为了兼顾查询效率而改用了双向链表。
在内存操作中,时间和空间是一对矛盾的存在,quicklist 采用了一个折中的方案,每个节点都是一个单独的压缩列表,整体是一个双向链表,内部是一个压缩列表,在空间和时间的矛盾中取得折中的结果。
汇总
本文介绍了 redis 主要数据结构的编码方式,从数据结构的出发理解其存储方式以及更换编码方式的阈值。