Redis源码阅读笔记--六大数据结构和五大对象

数据结构与对象

  • 一、 简单动态字符串

redis的字符串不是用‘\0’空字符串结尾的字符数组,而是sds动态字符串。每个sdshdr结构表示一个sds值,里面有buf字节数组,不是字符数组,有表示buf数组已使用字节数量的len和buf未使用字节数量的free属性。

1 sds可以常数复杂度获取字符串长度,因为有len属性。

2 可以减少修改字符串时带来的内存重分配次数。如拼接字符串不一定要再分配内存,截取字符串不一定回收内存。sds通过空间预分配优化字符串增长操作,如sds的len小于1MB,程序分配和len属性同样大小的未使用空间也就是free,大于1MB则分配1MB的未使用空间。惰性空间释放优化字符串缩短操作,字符串缩短时不会立即回收内存,会使用free属性将字节数量记录下来,等将来使用。

3 二进制安全:c语言的字符串只可以保存文本数据,sds因为是字节数组存储数据可以保存音频视频等二进制数据。

4 兼容c语言的字符串函数

  • 二、 链表

链表是列表键的底层实现之一,发布订阅功能也用到链表,Redis服务器用链表保存多个客户端信息。

链表由list结构和listNode结构组成,list结构有head属性保存指向表头的指针,tail属性保存指向表尾指针以及len属性记录链表长度。listnode结构为节点,使用void*指针保存节点值,所以链表具有多态性,每个节点保存的数据类型可以不一样。redis链表是双向无环的链表。

  • 三、 字典

字典被广泛用于实现Redis的各种功能,比如哈希键的底层实现之一,或是整个redis数据库用字典存储。

  • 字典实现:字典由俩个哈希表表示,一个平时使用一个rehash时使用。哈希表由数组元素为指针的数组表示,每个数组元素保存指向链表的指针,链表的每个节点存储一个键值对。通过键的哈希值然后计算出在数组的索引值,然后将键值对插入到相应索引数组指向的链表表头。有可能俩个以上的键被分配到同一个索引上,导致冲突,使用链地址法解决,将冲突的键值对连接成单向链表。

  • rehash:哈希表的键值对数量/数组的大小 = 负载因子。当redis没有执行GBSAVE命令或是GBREWRITEAOF时负载因子>1则执行扩展重哈希操作。当执行了后台进程则负载因子大于5再进行扩展重哈希操作。当负载因子小于0.1时收缩重哈希。
    1.为字典h[1]哈希表分配空间,如果是扩展操作,h[1]的
    大小为第一个大于h[0].used*2的2的n次方数。如果是收缩操作则是第一个大于h[0].used的2的n次方。2.重新计算h[0]中键的哈希值和索引值,根据索引值将键值对插入到h[1]哈希表的数组中。3.键值对迁移完之后将h[0]释放,将h[1]设置为h[0],创建新的空白哈希表作为下次rehash使用。

  • 四、跳跃表

跳跃表是一种有序数据结构,每个节点维持了多个指向其他节点的指针,可以快速访问其他节点。是有序集合的底层实现之一。

一、 跳跃表的实现:1.跳跃表由zskiplist结构和zskiplistNode结构组成。Zskiplist结构包含了跳跃表节点信息,header属性是表头节点,tail属性是表尾节点,level是指层数最大的节点的层数,还有节点的数量。通过zskiplist可以快速地访问表头或表尾或得到跳跃表长度。

2.zskiplistNode结构包含有层,后退指针,分值和成员对象属性。1.每个节点有1-32个随机数的层,每层有前进指针和跨度俩个属性,前进指针可以快速地访问其他节点,跨度记录俩个节点之间的距离,通过沿着前进指针遍历节点将跨度累加,可以得到目标节点在跳跃表的排位。2.后退指针指向上一个节点,用于逆序遍历使用。3.由于是有序数据结构,每个节点按照分值从小到大排列;4.每个节点都有一个唯一的成员对象,分值相同的节点按照成员对象的字典序大小排序。

  • 五、整数集合

整数集合(intset)是集合键的底层实现之一,当集合里只包含整数元素并且集合元素少时使用整数集合的底层实现。

整数集合intset结构里用数组以有序、无重复的方式保存集合元素。数组元素的编码有8位大小的整型,16位大小的整型和32为大小的正型。数组默认为8为大小整型编码,这样节约内存。但是一旦有更大的整数加入时数组会整体升级,变成16为大小整型数组或是32位。升级操作能节约内存,提升灵活性。

  • 六、压缩列表

压缩列表是列表键和哈希键的底层实现之一,当列表项少或是键值对少时使用压缩列表(ziplist)。压缩列表是redis为了节约内存开发的,由一系列连续内存块组成的数据结构。由zlbytes记录整个压缩列表所占内存字节数,zltail表示压缩列表起始地址距离表尾结点的字节数,直接定位到尾结点进行反向遍历,zllen表示压缩列表的节点个数,一个entry表示一个节点,存储了一个字节数组或是一个整数值。最后用zlend标记压缩列表的末端。

  • 七、对象

Redis通过在内存中存储键值对来表示数据库状态,键和值均为对象,每个对象都由一个redisObject结构表示,结构中有三个属性,type属性表示对象类型,encoding表示对象底层的数据结构的编码,ptr是指向底层数据结构的指针。

一、类型:通过TYPE 命令返回值对象类型。五大对象分别为:字符串对象,列表对象,哈希对象,集合对象和有序集合对象。

二、编码和底层实现:1.redis对同一个对象在不同的使用场景下设置不同的编码,也就是不同的底层数据结构实现,从而提高存储效率节约内存。比如列表键在列表项较少时使用ziplist压缩列表,在列表项多时使用链表。压缩列表由连续内存块组成不需要存下一个结点地址 节约内存并且不需要多次定位比双端链表更快载入到缓存中。当列表项增大时底层使用适合保存大量元素的双端链表。

三、对象的详细说明:1.字符串对象的编码可以是int、raw和embstr。对象保存的是整数值则编码为int,长字符串编码为raw,系统调用俩次内存分配创建redisObject和sds结构,短字符串则为embstr,系统调用一次内存分配创建连续空间,空间包括redisObject和sds结构。对象会有编码转换。

2.列表对象的编码可以是ziplist(压缩列表)和linkedlist(链表),当列表项少时使用压缩列表,每个entry代表一个列表元素,可以是字节数组或是整数。当列表项多时使用链表编码,每个节点保存一个字符串对象,一个字符串对象保存一个列表元素。

3.哈希对象:哈希对象在键值对少时编码为ziplist(压缩列表),键作为压缩列表的节点推入到压缩列表表尾,然后值推入到表尾,键值对在相邻的内存中。当键值对多时编码为hashtable(字典),每个键为字符串对象保存键,每个值为字符串对象保存值。

4.集合对象:集合对象里所有元素是整数时使用intset整数集合作为底层实现,所有整数以有序的方式保存在数组中。当元素含字符串时使用hashtable字典作为编码,字典键是字符串对象,值为空null,每个字符串对象保存一个集合元素。

5.有序集合对象:当集合元素数量较少时使用压缩列表实现,每个集合元素使用俩个连续的压缩列表节点存储,第一个节点保存元素成员,第二个保存分值。元素按照分值从小到大排序。集合元素较多时使用skiplist跳跃表实现,每个跳跃表节点保存了一个集合元素,存储了成员和分值。还维护了一个字典,键为集合元素成员,值为分数,这样可以O(1)复杂度得到给定成员的分值,ZSCORE通过此特性实现。

  • 四. 内存回收和对象共享

1.Redis在自己的对象系统中利用了引用计数算法实现了内存回收机制。每个对象的redisObject结构里都由refCount属性,创建一个对象其引用计数值被初始化为1,之后被对象引用一次则加一,不被引用则减一。当refCount变为0时,对象占用内存会被回收。

2.refCount属性还可以实现对象共享功能,为了节约内存,在多个键的值对象相同的情况下,多个键的值指针指向同一个值对象,实现对象共享,该对象的refCount为键的个数。Redis在初始化服务器时会创建一万个字符串对象,均为0-9999的整数值,服务器需要使用这些字符串对象时会引用而不是创建新对象,节约内存提高速度。

3.redisObject里还有lru属性,记录最后一次命令程序访问的时间,可以得到对象的空转时长。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页