• redis(3)跳跃表与整数集合,压缩列表


    跳跃表:
    跳跃表是一种有序数据结构,通过在每个节点维持多个指向其他节点的指针,达到快速访问节点的目的。redis使用跳跃表作为有序集合键的实现,如果一个有序集合包含额元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,redis会使用

    跳跃表作为有序集合键的实现。

    redis只在有序集合键和集群节点中用作内部数据结构。

    跳跃表zskiplist{

    zskiplistnode header :指向跳跃表的表头节点。

    zskiplistnode tail:指向跳跃表的表尾节点

    level:记录目前跳跃表,层数最大的节点所在层数

    length:记录跳跃表长度,不包含表头节点

    }

    zskiplistnode{

    zskiplistnode *backward 后退指针,在程序从表尾向前遍历时使用

    score 分数

    *obj 成员对象

    zslistlevel{

    zskiplistnode *forward 访问位于表尾方向的其他节点

    int span 表示当前节点与本层所指向的下一个节点的距离

    }

    }

    节点的分值是一个double类型的浮点数,跳跃表中的所有节点都按分值大小排序

    节点的成员对象是一个指针,它指向一个字符串对象,而字符串对象则保存着一个sds值。

    同一个跳跃表,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值可以是相同的:分值相同的节点将按照成员对象在字典序中的大小来进行排序,成员对象较小的节点会排在前面,靠近表头方向,而成员对象

    较大的节点则会排在后面。靠近表尾方向。

    整数集合:
    整数结合是集合键的底层实现,

    intset{

    encoding 编码方式:int16_t  int32_t  int64_t

    int8_t contents[] 保存元素的数组    //contents数组所保存的值取决于编码的方式,且数组中的元素都是从小到大排序的

    }

    整数集合的升级操作,如果向数组中元素添加了编码方式更高的整数,则会对整数集合进行升级

    根据新元素的类型,扩展底层数组空间的大小,并为新元素分配空间,将底层数组现有的元素进行类型转换,转换到与新元素相同的类型,并将转换后的元素放在正确的位置上,

    这个正确的位置,要维持数组中元素的大小排序,最后一步需要改变encoding的值为最高类型的值

    整数集合的优点是可以通过自动升级底层数组来适应新元素,可以随意将不同类型编码的整数,加入到集合中。同时

    可以节约内存,只有在添加更高编码的元素时才会升级集合,可以尽量节省内存。

    整数集合不支持降级操作。

    压缩列表:

    压缩列表是哈希键与列表键的底层实现,当一个列表键只包含少量列表项,并且列表项要么就是小整数值,长度比较短的字符串,那么reids会使用压缩列表来做列表键的底层实现。
    压缩列表是redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序性数据结构,一个压缩列表可以包含任意多个节点,每个节点保存一个字节数组或者整数值

    压缩列表中的压缩节点即entryx的构成:
    {

    previous_entry_length

    encoding

    content

    }entryx

    previous_entry_length 保存的是前一个字节的长度,如果前一个节点的长度<254字节,那么previous_entry_length属性的长度为一字节(注:前一个节点的长度>=254字节,指实际分配内存空间大于254字节,而我们这里所说的属性长度为一字节仅仅是用来记录这个长度值,相当于记录数字,这里容易弄混),如果前一个节点长度>=254字节,那么previous_entry_length属性的长度为5字节,其中属性的第一字节会被设置为0xEE(十进制254),而之后的四个字节则用于保存前一字节的长度

    压缩列表可以通过表尾指针所指向表尾的节点减去当前及节点的previous_entry_length值,即可以得到前一个节点所在位置,这就是压缩列表从表尾向表头的遍历操作

    压缩列表存在连锁更新问题:即如果在表头插入一个大于254字节长度的节点,原来表头节点的previous_entry_length属性仅为一字节无法保存现表头长度,则需要变化扩展previous_entry_length属性为5字节,如果恰好当前节点长度介于250到253之间,则扩展后使得当前节点长度>=254字节,使得下一个节点也无法保存,继续扩展下一个节点可能出现连锁反应,引发由内存空间重分配引发的性能问题。

    redis对象:
    redis没有使用之前讲到的数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,系统包含了 字符串对象,列表对象,哈希对象,集合对象,有序集合对象,五种类型

    reredis使用对象来表示数据库中的键和值,每次使用reids的数据库创建一个键值对时,至少会创建两个对象,

    redisobject结构如下所示:
    redisobject{

    unsigned type :上面提到的五种类型

    encoding:编码:对向的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定
    void * ptr 指向底层实现数据结构的指针

    }

    记住每一个种对象所使用的的编码方式中种类可以有多种,例如 String 对象编码方式可以有 整数值或者采用embstr编码的简单动态字符串实现

    即 redis_striing 编码方式有 redis_encoding_int 和 redis_encoding_embstr

    使用object encoding 命令可以查看一个数据库键的值对象的编码

    字符串对象的编码可以是int raw embstr

    当字符串对象所保存字符串的值小于等于39字节,那么将采用embstr编码

    embstr编码是专门用于保存段字符串的一种优化编码方式,这种编码和raw编码一样都是用redisobject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,

    空间中依次包含redisobject和sdshdr两个结构。embstr编码优点将次分配内存和释放内存对于系统调用的次数,同时enmstr编码的字符串对象所有的数据都保存在一块连续的内存里面,所以采用embstr编码的字符串对象比raw编码的字符串对象

    能够更好地利用缓存带来的优势。

    列表对象可以采用压缩列表与双端链表编码:
    当列表对象可以满足下列两个条件时,列表对象可以使用ziplist编码:
    列表对象所保存的所有字符串元素长度都小于64字节,

    列表对象保存的元素数量小于512个,

    如果不能满足这两个条件,则会采用linkedlist编码。

    哈希对象的编码可以是ziplist和hashtable

    对于压缩列表:保存了同一个键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值得节点在后;先添加到哈希对象中的键值对会被放到压缩列表的表头方向,而后来添加到哈希表对象中的键值对会被放到

    压缩列表的表尾方向。

    hashtable编码的哈希对象底层使用字典作为顶层实现。哈希对象中的每个键值对使用一个字典键值对来保存

    编码转换:
    当哈希对象保存的所有键值对的键和值的字符串长度都小于64字节,

    哈希对象保存的键值对数量小于512个,不能满足这两个条件的哈希对象需要使用hashtable编码

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

    如果是使用hashtable编码作为集合的底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为null。

    有序集合对象:
    编码可以是ziplist和skiplist

    压缩列表作为顶层实现,则压缩列表内的集合元素按照分值从小到大进行排序,分值较小的元素被放置在靠近表头的位置,而分值较大的元素则被放在靠近表尾的位置

    较复杂的是skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表

    zet{

    zskiplist *zsl;

    dict *dict

    }zset

    对于zskiplist使用跳跃表节点的score属性保存分值,object属性保存元素成员

    对于字典使用,字典的键保存了元素的值,字典的值保存了元素的得分

    这样的目的是使用zskiplist可以降低范围查询的时间复杂度:如执行zrank,zrange命令

    使用dict的原因是可以以O(1)的时间复杂度的到元素的分值,如果上述两只结构只使用一个的话,必然会导致某一个命令的时间复杂度上升。

    有序集合对象可以同时满足一下两个条件时对象会使用ziplist编码:
    有序集合对象保存元素数量小于128

    有序集合保存的所有元素成员的长度都小于64字节

    redisobject对象中内存回收:

    redis在自己的对象系统中构建了一个引用计数技术实现内存的回收机制,通过这一机制,程序通过跟踪对象的引用计数信息,在适当的时候自动释放对象进行内存回收

    每个对象的引用计数用redisobject对象的refcount 属性记录

    创建一个新对象时,引用计数值会被初始化为1

    当对象被一个新程序使用时,它的引用计数值会被增1

    当对象不再被一个程序使用时,它的引用计数值减一

    当对象的引用计数值为0时,对象占用的内存会被释放

    redisobject对象中对象共享:
    对象的引用计数属性还带有对象共享的作用,如果键a创建了一个包含整数值100的字符串对象作为值对象,如果键b也要创建一个同样保存了整数值100的字符串对象作为值对象,

    那么键b则会和a共享一个字符串对象

    实现步骤:
    将数据库键的值指针指向一个现有的值对象,将被共享的值对象的引用计数增1

    目前来说redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从1到9999的所有整数值,当服务器需要用到0到9999的字符串对象时,服务器就会使用这些共享对象,而不是创建新对象

    对象的空转时长:
    redisobject.lru属性:该属性记录了对象最后一次被命令程序访问的时间。

    通过使用object idletime 命令可以打印出给定键的空转时长,这一空转时长就是通过将当前时间减去键的值对象的lru时间计算得到

    键的空转时长还有一个作用就是如果服务器打开了maxmemory选项,并且服务器用于内存回收的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,

    空转时长较高的那部分键会优先被服务器释放,从而回收内存


     

  • 相关阅读:
    文件上传
    使用servlet+jdbc+MD5实现用户加密登录
    JDBC入门
    springmvc(三)
    springmvc(二)
    springmvc(一)
    JavaScript总结(一)
    Spring的AOP面向切面编程
    Spring框架(三)
    Spring框架(二)
  • 原文地址:https://www.cnblogs.com/foreverlearnxzw/p/13788620.html
Copyright © 2020-2023  润新知