Redis的底层结构:
String,list,set,hash,sortedset。这五种仅仅是redis的对象类型,redis是一个对象系统,并没有直接使用底层的数据结构,而是以对象的形式来存储的。
Redis的底层数据结构,一共有7种,
- String:
Sdshdr{ Int len;记录了buf中已使用的字节数量,是实际长度 Int free;buf数组中未使用的字节数量 Char buf[];保存字符串 }
已使用长度>1mb,则free=1mb 7.惰性空间释放:当sds空间需要缩短时,free就会记录下这些长度,实际空间并不会变小 |
- 链表
1.c中并没有内置的链表,所有redis自己实现了链表。 2.链表是由多个链表节点组成的,双向的 Listnode{ Listnode *preev 前直节点 Listnode *next 后直接点 Void *value 节点值 }
List{ Listnode *head 表头结点 Listnode *tail 表未节点 Int len 链表中节点数量 Dup() 复制节点 Free() 释放节点 Match() 判断是否相等 }
双向,无环,带表头表尾,长度 计数器,多态 |
- 字典map(散列表,数组+链表)
dictEntry{ Key:key V:value Next:下一个节点 }
Dictht{ dictEntry table[]; Long size;哈希表大小 Long Sizemask;用于计算索引值Sizemask=use-1; Long use;已使用的节点数量 }
Dict{ Dictht ht[2] }
收缩:第一个大于等于use的(2的n次方)
执行bgsave或者bgrewriteaof,负载因子大于等于5 负载因子<0.1,自动收缩 12.渐进rehash,字典的扩展并不是一次性完成的,而是渐进的。 |
4.跳跃表
1.跳跃表(skiplist)是一种有序的数据结构,它通过每个节点中维持多个指向其他节点的指针,从而实现快速访问。跳跃表平均O(logN),最坏O(N),支持顺序遍历查找。在redis中,有序集合(sortedset)的其中一种实现方式就是跳跃表。 2.当有序集合的元素较多,或者集合中的元素是比较常的字符串,则会使用跳跃表来实现。 3.跳跃表节点 zskiplistNode{ Robj obj;成员对象 Double score 分值 zskiplistNode backword;后退指针
zskiplistLevel{ zskiplistNode forword;前进指针 Int span;跨度,记录前进指针所指的节点和当前节点的距离,用于计算排位,在查找某个节点的过程中,将沿途访问过的层的跨度累计,得到的结果就是跳跃表的排位 } }
Zskiplist{ zskiplistNode header,tail; Int length; Level;表中最大层数 } 5.跳跃表 6.层,每个层都有两个属性,前进指针和跨度,每个节点的层数是1--32的随机数 7.分值和成员: 按分值从小到大排列,成员指向一个对象分值相同时,成员对象较小的回排在靠近标题的方向 |
- 整数集合intset
1.整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。 2.它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素 3. intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; } 4.contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。 5.升级 每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上(从后往前),而且在放置元素的过程中,需要继续位置底层数组的有序性质不变。 将新元素添加到底层数组里面。 将encoding属性更改。 整数集合添加新元素的时间复杂度为O(N)。 因为引发升级的元素要么最大要么最小,所有它的位置要么是0要么是length-1。 升级的好处: 提升整数集合的灵活性,可以随意将int16,int32,int64的值放入集合。 尽可能地节约内存 降级: 整数集合不支持降级操作 |
- 压缩列表
1.压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。 2.
3.每个压缩列表节点可以保存一个字节数组或者一个整数值,而每个节点都由previous_entry_length、encoding、content三个部分组成。 4.previous_entry_length:节点的previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。因为有了这个长度,所以程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址。压缩列表的从表尾向表头遍历操作就是使用这一原理实现的。 5.encoding:记录了节点的content属性报错数据的类型和长度 6.content:负责保存节点的值,节点值可以是一个字节数组或者整数 7.连锁更新:因为previous_entry_length属性可能是一个或者是五个字节,插入删除都会引起压缩列表空间的重分配 |
- 对象
4. Redisobjetc{ Type:4;五种对象类型 Encoding:4;编码 *ptr;执行底层实现数据结构的指针,有encoding决定 Int refcount;引用计数器 }
其中type可以指向下表中的任何一个类型
Encoding属性记录了对象所使用的编码,也就是说,这个对象使用了什么数据结构作为对象的底层实现
Int:如果保存的是整数类型,并且这个整数可以用long表示 Raw:如果字符串长度大于39个字节,就会使用raw Emstr:是专门用于保存段字符串的一种优化编码方式。
因为raw保存的是长字符串,所需内存较大,所以创建对象时,需要分配两次内存,一次分配redisobjetc对象所需内存,一次分配字符串对象所需内存。 Emstr只需要分配一次内存,redisobject和字符串所需内存分配一块连续内存 Emstr是只读的,redis并未提供修改emstr的方式,对emstr的修改实际上是先转换raw,在进行修改 Int和emstr在满足条件时,会自动转化为raw
Long和double也是作为字符串存储的
当列表对象同时满足下面两个条件时,则使用ziplist: 所有字符串元素的长度都小于64字节 元素数量小于512 优缺点:ziplist是一种压缩列表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。如下图所示,对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次,它们在内存中是一块连续的区域。 linkedlist是一种双向链表。它的结构比较简单,节点中存放pre和next两个指针,还有节点相关的信息。当每增加一个node的时候,就需要重新malloc一块内存。
Ziplist:每当添加一个新值,都会添加到尾部,key尾,后value也被添加到尾,所有,key-value是紧挨着的。 当列表对象同时满足下面两个条件时,则使用ziplist: 所有键值对的键和值的字符串度都小于64字节 键值对数量小于512
满足下面两个条件,使用intset: 所以有元素都是整数值 元素数量不超过512个
dict字典为有序集合创建了一个成员到分值的映射。给一用O(1)的时间查到分值。 当有序集合对象同时满足下面两个条件时,则使用ziplist: 所有元素的字符串度都小于64字节 元素数量小于128
类型检查通过redisobject的type属性实现的
|