作者:CodeBear
来源:www.cnblogs.com/CodeBear/p/13446015.html

Redis之所以那么快,还有一个很重要、但是经常被大家忽视的一点,那就是Redis精心设计的数据结构,本文给大家介绍下Redis另外一种底层数据结构:ziplist

在Redis中,有五种基本数据类型,除了 String,还有list,hash,zset,set,其中list,hash,zset都间接或者直接使用了ziplist,所以说理解ziplist也是相当重要的。

ziplist是什么意思

我刚开始看ziplist的时候,总觉得zip这个单词甚是熟悉,好像在日常使用电脑的时候经常看到,就是“压缩”的意思,那ziplist就可以翻译成“压缩列表”了。

为什么要有ziplist

有两点原因:

  • 普通的双向链表,会有两个指针,在存储数据很小的情况下,我们存储的实际数据的大小可能还没有指针占用的内存大,是不是有点得不偿失?而且Redis是基于内存的,而且是常驻内存的,内存是弥足珍贵的,所以Redis的开发者们肯定要使出浑身解数优化占用内存,于是,ziplist出现了。

  • 链表在内存中,一般是不连续的,遍历相对比较慢,而ziplist可以很好的解决这个问题。

来看看ziplist的存在

zadd programmings 1.0 go 2.0 python 3.0 java

创建了一个zset,里面有三个元素,然后看下它采用的数据结构:

debug object  programmings"Value at:0x7f404ac30c60 refcount:1 encoding:ziplist serializedlength:36 lru:2689815 lru_seconds_idle:9"
HSET website google "www.g.cn

创建了一个hash,只有一个元素,看下它采用的数据结构:

debug object website
"Value at:0x7f404ac30ac0 refcount:1 encoding:ziplist serializedlength:30 lru:2690274 lru_seconds_idle:14"

可以很清楚的看到,zset和hash都采用了ziplist数据结构。

当满足一定的条件,zset和hash就不再使用ziplist数据结构了:

debug object website
"Value at:0x7f404ac30ac0 refcount:1 encoding:hashtable serializedlength:180 lru:2690810 lru_seconds_idle:2"

可以看到,hash的底层数据结构变成了hashtable。szet就不做实验了,感兴趣的小伙伴们可以自己实验下。至于这个转换条件是什么,放到后面再说。如果你对 Redis 感兴趣,可以关注公众号互联网架构师看更多 Redis 系列教程。

好奇的你们,肯定会尝试看下list的底层数据结构是什么,发现并不是ziplist:

LPUSH languages python
debug object languages
"Value at:0x7f404c4763d0 refcount:1 encoding:quicklist serializedlength:21 lru:2691722 lru_seconds_idle:22 ql_nodes:1 ql_avg_node:1.00 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:19"

可以看到,list采用的底层数据结构是quicklist,并不是ziplist。

在低版本的Redis中,list采用的底层数据结构是ziplist+linkedList,高版本的Redis中,quicklist替换了ziplist+linkedList,而quicklist也用到了ziplist,所以可以说list间接使用了ziplist数据结构。

探究ziplist

ziplist源码:https://github.com/redis/redis/blob/unstable/src/ziplist.c

ziplist源码的注释写的非常清楚,如果英语比较好,可以直接看上面的注释。

ziplist布局

<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

这是在注释中说明的ziplist布局,我们一个个来看,这些字段是什么:

  • zlbytes:32bit无符号整数,表示ziplist占用的字节总数(包括

    本身占用的4个字节);

  • zltail:32bit无符号整数,记录最后一个entry的偏移量,方便快速定位到最后一个entry;

  • zllen:16bit无符号整数,记录entry的个数;

  • entry:存储的若干个元素,可以为字节数组或者整数;

  • zlend:ziplist最后一个字节,是一个结束的标记位,值固定为255。

Redis通过以下宏定义实现了对ziplist各个字段的存取:

// 假设char *zl 指向ziplist首地址
// 指向zlbytes字段
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))// 指向zltail字段(zl+4)
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))// 指向zllen字段(zl+(4*2))
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))// 指向ziplist中尾元素的首地址
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))// 指向zlend字段,指恒为255(0xFF)
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

entry的构成

从ziplist布局中,我们可以很清楚的知道,我们的数据被保存在ziplist中的一个个entry中,我们下面来看看entry的构成。

<prevlen> <encoding> <entry-data>

我们再来看看这三个字段是什么:

  • prevlen:前一个元素的字节长度,便于快速找到前一个元素的首地址,假如当前元素的首地址是x,那么(x-prevlen)就是前一个元素的首地址。

  • encoding:当前元素的编码,这个字段实在是太复杂了,我们放到后面再说;

  • entry-data:实际存储的数据。

prevlen

prevlen字段是变长的:

  • 前一个元素的长度小于254字节时,prevlen用1个字节表示;

  • 前一个元素的长度大于等于254字节时,prevlen用5个字节进行表示,此时,prevlen的第一个字节是固定的254(0xFE)(作为这种情况的一个标志),后面4个字节才表示前一个元素的长度。

encoding

下面就要介绍下encoding这个字段了,在此之前,大家可以到阳台吹吹风,喝口热水,再做个深呼吸,最后再做一个心理准备,因为这个字段实在是太复杂了,搞不好,看的时候,一下子吐了。。。

如果实在无法理解,直接略过这一段吧。

Redis为了节约空间,对encoding字段进行了相当复杂的设计,Redis通过encoding来判断存储数据的类型,下面我们就来看看Redis是如何根据encoding来判断存储数据的类型的:

  1. 00xxxxxx 最大长度位 63 的短字符串,后面的6个位存储字符串的位数;

  2. 01xxxxxx xxxxxxxx 中等长度的字符串,后面14个位来表示字符串的长度;

  3. 10000000 aaaaaaaa bbbbbbbb cccccccc dddddddd 特大字符串,需要使用额外 4 个字节来表示长度。第一个字节前缀是10,剩余 6 位没有使用,统一置为零;

  4. 11000000 表示 int16;

  5. 11010000 表示 int32;

  6. 11100000 表示 int64;

  7. 11110000 表示 int24;

  8. 11111110 表示 int8;

  9. 11111111 表示 ziplist 的结束,也就是 zlend 的值 0xFF;

  10. 1111xxxx 表示极小整数,xxxx 的范围只能是 (0001~1101), 也就是1~13

如果是第10种情况,那么entry的构成就发生变化了:

<prevlen> <encoding>

因为数据已经存储在encoding字段中了。

可以看出Redis根据encoding字段的前两位来判断存储的数据是字符串(字节数组)还是整型,如果是字符串,还可以通过encoding字段的前两位来判断字符串的长度;如果是整形,则要通过后面的位来判断具体长度。

entry的结构体

我们上面说了那么多关于entry的点点滴滴,下面将要说的内容可能会颠覆你三观,我们在源码中可以看到entry的结构体,上面有一个注释非常重要:

/* We use this function to receive information about a ziplist entry.* Note that this is not how the data is actually encoded, is just what we* get filled by a function in order to operate more easily. */
typedef struct zlentry {unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/unsigned int prevrawlen;     /* Previous entry len. */unsigned int lensize;        /* Bytes used to encode this entry type/len.For example strings have a 1, 2 or 5 bytesheader. Integers always use a single byte.*/unsigned int len;            /* Bytes used to represent the actual entry.For strings this is just the string lengthwhile for integers it is 1, 2, 3, 4, 8 or0 (for 4 bit immediate) depending on thenumber range. */unsigned int headersize;     /* prevrawlensize + lensize. */unsigned char encoding;      /* Set to ZIP_STR_* or ZIP_INT_* depending onthe entry encoding. However for 4 bitsimmediate integers this can assume a rangeof values and must be range-checked. */unsigned char *p;            /* Pointer to the very start of the entry, thatis, this points to prev-entry-len field. */
} zlentry;

重点看上面的注释。一句话解释:这个结构体虽然定义出来了,但是没有被使用,因为如果真的这么使用的话,那么entry占用的内存就太大了。

ziplist的存储形式

Redis并没有像上篇博客介绍的SDS一样,封装一个结构体来保存ziplist,而是通过定义一系列宏来对数据进行操作,也就是说ziplist是一堆字节数据,上面所说的ziplist的布局和ziplist中的entry的布局只是抽象出来的概念。

为什么不能一直是ziplist

在文章比较前面的部分,我们做了实验来证明,满足一定的条件后,zset、hash的底层存储结构不再是ziplist,既然ziplist那么牛逼,

Redis的开发者也花了那么多精力在ziplist的设计上面,为什么zset、hash的底层存储结构不能一直是ziplist呢?

因为ziplist是紧凑存储,没有冗余空间,意味着新插入元素,就需要扩展内存,这就分为两种情况:

  • 分配新的内存,将原数据拷贝到新内存;

  • 扩展原有内存。

所以ziplist 不适合存储大型字符串,存储的元素也不宜过多。

ziplist存储界限

那么满足什么条件后,zset、hash的底层存储结构不再是ziplist呢?

在配置文件中可以进行设置:

hash-max-ziplist-entries 512  # hash 的元素个数超过 512 就必须用标准结构存储
hash-max-ziplist-value 64  # hash 的任意元素的 key/value 的长度超过 64 就必须用标准结构存储
zset-max-ziplist-entries 128  # zset 的元素个数超过 128 就必须用标准结构存储
zset-max-ziplist-value 64  # zset 的任意元素的长度超过 64 就必须用标准结构存储

对于这个配置,我只是一个搬运工,并没有去实验,毕竟没有人会去修改这个吧,感兴趣的小伙伴可以试验下。

ziplist元素太多,怎么办

在介绍ziplist布局的时候,说到ziplist用两个位来记录ziplist的元素个数,如果元素个数实在太多,两个位不够怎么办呢?这种情况下,求ziplist元素的个数只能遍历了。

看到了吧,Redis真不是想象中的那么简单,需要研究的东西还是挺多,也挺复杂的,如果我们不去学习,可能觉得自己完全掌握了Redis,但是一旦开始学习了,才发现我们先前掌握的只是皮毛。

验证了一句话,知道的越多,不知道的越多。如果你对 Redis 感兴趣,可以关注公众号互联网架构师看更多 Redis 系列教程。

关微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。

猜你喜欢

1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBook发起和总结

2、如何才能成为优秀的架构师?

3、从零开始搭建创业公司后台技术栈

4、程序员一般可以从什么平台接私活?

5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6、滴滴业务中台构建实践,首次曝光

7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

8、15张图看懂瞎忙和高效的区别

9、2T架构师学习资料干货分享

Redis 那么快之底层 ziplist 的奥秘!相关推荐

  1. 字节跳动面试真题:Redis为什么快?

    前言 大家好,有位伙伴面试了字节(四年半工作经验),分享下面试真题,大家一起加油哈. 说说Redis为什么快 Redis有几种数据结构,底层分别是怎么存储的 Redis有几种持久化方式 多线程情况下, ...

  2. 三、redis原理之list底层数据结构

    一.redis原理之list底层数据结构ziplist和quicklist. 快速列表 quicklist[quicklist = 链表+ziplist] 首先在列表元素较少的情况下会使用一块连续的内 ...

  3. 面试官问我为什么Redis这么快,我.......

    开课开课~ 面试官:为什么项目中用Redis? 我:当然是因为Redis好啊 面试官:emmm.....那Redis哪里好? 我:因为Redis快啊. 面试官:(这小伙子有点彪啊...)那为什么Red ...

  4. Redis 数据类型之(底层解析)

    Redis 数据类型之(底层解析) Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redi ...

  5. 【重难点】【Redis 01】为什么使用 Redis、Redis 的线程模型、Redis 的数据类型及其底层数据结构

    [重难点][Redis 01]为什么使用 Redis.Redis 的线程模型.Redis 的数据类型及其底层数据结构 文章目录 [重难点][Redis 01]为什么使用 Redis.Redis 的线程 ...

  6. 四、redis原理之set底层数据结构

    一.redis原理之set底层数据结构? 其底层有两种实现方式: 1.当value是整数值时,且数据量不大时使用inset来存储, 2.其他情况都是用字典dict来存储 inset的结构: typed ...

  7. 一文深入了解 Redis 内存模型,Redis 的快是有原因的!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:编程迷思 cnblogs.com/kismetv/p/865 ...

  8. redis 内存不足 排查_一文深入了解 Redis 内存模型,Redis 的快是有原因的!

    前言 一.Redis内存统计 二.Redis内存划分 1.数据 2.进程本身运行需要的内存 3.缓冲内存 4.内存碎片 三.Redis数据存储的细节 1.概述 2.jemalloc 3.redisOb ...

  9. Redis中ZSet的底层数据结构跳跃表skiplist,你真的了解吗?

    欢迎大家关注我的微信公众号[老周聊架构],Java后端主流技术栈的原理.源码分析.架构以及各种互联网高并发.高性能.高可用的解决方案. 一.前言 老周写这篇文章的初衷是这样的,之前项目中有大量使用 R ...

  10. 【决战西二旗】|Redis面试热点之底层实现篇

    来自:后端技术指南针 0.前言 最近一周没有技术文章产出,主要是Q4马上结束各种业务都在冲量,笔者一直都在疯狂工作甚至还有些焦虑到偶尔失眠,由于没有成块的时间研究新东西,所以就把之前看过的东西抽时间总 ...

最新文章

  1. c语言新增学生信息无法输入密码,c语言问题,希望大家帮帮忙,急
  2. [optee]-opteeTA启动的过程(open_ta的过程)
  3. java通过commons-fileupload实现多张图片的上传(jsp页面)
  4. 检查电脑是否被安装木马三个小命令
  5. VueSummary_note
  6. 炒冷饭系列:设计模式 工厂模式
  7. python基础篇--从零开始(第一个程序)
  8. 正则表达式--简单记忆一
  9. 用Nginx禁止指定IP、国外IP访问我的网站
  10. 土地利用转移矩阵图怎么做_如何用Arcgis做土地利用转移矩阵?求教各位..._土地估价师_帮考网...
  11. B-JUI 实践 之 带搜索与编辑的Datagrid
  12. 【测试】你的浏览器HOLD住HTML5吗?
  13. 北漂IT男返乡2年的三线楼市观察(宜昌夷陵篇)-原创
  14. 大S首度曝光女儿正面照:小小幸福分享给大家
  15. 强制删除windows文件的终极解决方法
  16. ps切出来的图片导出来只有一张是png格式的其他全是jpg格式,怎么样让所有的切片变成png格式。
  17. UML统一建模语言第4章 用例和用例图课后习题
  18. Eclipse oxygen 版本汉化教程
  19. 水木清华,telnet挂积分,工具
  20. 网络安全评估职业技能师资培训(1)网络安全法律法规

热门文章

  1. Java常见的垃圾收集器GC算法整理
  2. 英语发音规则---U字母-[复习中]
  3. java XML 通过BeanUtils的population为对象赋值 根据用户选择进行dom4j解析
  4. [Android Pro] 内容提供者ContentProvider的基本使用
  5. 巨杉数据库入选Gartner数据库报告,中国首家入选厂商
  6. React Native : AsyncStorage 存储
  7. 本人译著《Professional Xcode 3》现已翻译完毕
  8. 「leetcode」本周小结!(贪心算法系列四)
  9. hdu 4318 Power transmission 临接表 广搜 多校联合赛(二) 第九题
  10. InDesign 软件教程,如何新建文档?