Redis设计与实现读书笔记

Implementation of Redis

Posted by wykxwyc on October 1, 2022

目录


Redis数据类型

Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。

键的类型只能为字符串,值支持以下五种数据类型:
String: 字符串
Hash: 散列
List: 列表
Set: 集合
Sorted Set: 有序集合。

Redis内数据结构的实现

SDS

Redis使用简单动态字符串SDS(simple dynamic string)作为字符串表示。
比起C字符串,SDS具有以下优点:
1)常数复杂度获取字符串长度。
2)杜绝缓冲区溢出。
3)减少修改字符串长度时的内存重分配次数。
4)二进制安全。
5)兼容部分C字符串函数。

list
  • 链表被广泛用于实现Redis的各种功能,比如列表键、发布和订阅、慢查询、监视器等。
  • 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。
  • 每个链表使用一个list结构来表示,这个结构带有表头节点指针、表尾节点指针以及链表长度等信息。
  • 因为链表表头节点的前置节点和标为节点的后置节点都指向NULL,所以Redis的链表实现的是无环链表。
  • 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。
dict
  • 字典被广泛用于实现Redis的各种功能,其中包含数据库和哈希键。
  • Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。
  • 当字典被用作数据可的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法计算键的哈希值。
  • 哈希表使用连地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表。
  • 在对哈希表进行扩展或收缩操作时,程序需要将现有哈希表包含的所有键值对rehash到新哈希表里面,并且这个rehash过程并不是一次性地完成的,二十渐进式地完成的。
skiplist
  • 跳跃表是有序结合的底层实现之一。
  • Redis的跳跃表由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
  • 每个跳跃表节点的层高都是1至32之间的随机数。
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。
  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。
intset
  • 整数集合是集合键的底层实现之一。
  • 整数集合的底层实现为数组,这个数组以有序、无重复的方式保存集合元素,在有需要时,程序会更据新添加元素的类型,改变这个数组的类型。
  • 升级操作为整数集合带来了操作的灵活性,并且尽可能地节约了内存。
  • 整数集合只支撑升级操作,不支持降级操作。
ziplist
  • 压缩列表是一种为节约内存而开发的顺序型数据结构。
  • 压缩列表被用作列表键和哈希键的底层实现之一。
  • 压缩列表可以包含多个节点,每个结点可以保存一个字节数组或者整数值。
  • 添加新节点到压缩列表,或者聪压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高。

REDIS对象类型

对象类型包含如下几种:
String: 字符串
List: 列表
Hash: 哈希(散列)
Set: 集合
Sorted Set: 有序集合

字符串对象

字符串对象的编码可以是int, raw, 或者embstr。

  • 如果字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面,并将字符串对象的编码设置为int。
  • 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于39字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。
  • 如果字符串对象保存的是一个字符串值,并且这个字符串长度小于等于39字节,那么字符串对象将使用embstr编码方式来保存这个字符串值。
列表对象

列表对象的编码可以是ziplist或者linkedlist。

  • ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素。
  • linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。
  • 当列表对象可以妈祖以下两个条件时,列表对象使用ziplist编码。1)列表对象保存的所有字符串元素长度都小于64字节;2)列表对象保存的元素数量小于512个;不能满足这两个条件的列表对象需要使用linkedlist编码。
哈希对象

哈希对象的编码可以是ziplist或者hashtable。

  • ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾。
  • hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存,字典中每个键(或值)都是一个字符串对象,对象中保存了键值对的键(或值)。
  • 当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码。1)哈希对象保存的所有简直对的键和值的字符串长度都小于64字节。2)哈希对象保存的键值对数量小于512个,不能满足这两个条件的哈希兑现需要使用hashtable编码。
集合对象

集合对象的编码可以是intset或者hashtable。

  • intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。
  • hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL。
  • 当集合对象可以同时满足以下两个条件时,对象使用intset编码。集合对象保存的所有元素都是整数值,集合对象保存的元素数量不超过512个。不能满足这两个条件的集合对象需要使用hashtable编码。
有序集合对象

有序集合对象的编码可以是ziplist或者skiplist。

  • ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保存元素的分值(score)。
  • skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。
  • 当有序集合对象可以满足以下两个条件时,对象使用ziplist编码。1)有序集合保存的元素数量小于128个。2)有序集合保存的所有元素成员长度都小于64字节。不能满足以上两个条件的有序集合对象将使用skiplist编码。

Redis持久化

Redis持久化是指什么

Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。

RDB 持久化

(RDB)Redis DB
将某个时间点的所有数据都存放到硬盘上。
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长。

AOF 持久化

将写命令添加到 AOF 文件(Append Only File)的末尾。

使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。
这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。

AOF同步选项

选项 同步频率
always 每个写命令都同步
everysec 每秒同步一次
no 操作系统决定何时同步

Redis内部rehash的实现

重哈希的实现步骤:
1.选择重哈希是要减小哈希表的表长还是增大表长?
2.将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上面: rehash 指的是重新计算键的哈希值和索引值, 然后将键值对放置到 ht[1] 哈希表的指定位置上。
3.当 ht[0] 包含的所有键值对都迁移到了 ht[1] 之后 (ht[0] 变为空表), 释放 ht[0] , 将 ht[1] 设置为 ht[0] , 并在 ht[1] 新创建一个空白哈希表, 为下一次 rehash 做准备。

参考文献

1.《Redis设计与实现》-黄健宏著
2.github.com/CyC2018/CS-Notes
3.What Redis data structures look like
4.http://redisbook.com/preview/dict/rehashing.html