zoukankan      html  css  js  c++  java
  • Redis08:redis中的对象与存储形式

    对象

    redis中所有数据都是以对象的形式存在的,而不是直接使用之前提到过的各种数据结构,使用对象的好处主要有几点:

    1、可以很方便的判断对象的类型,进而判断是否可以执行给定的命令。

    2、一个类型可以使用不同的底层结构,切换起来很灵活,优化对象在不同场景下的使用效率。

    3、基于对象系统,引入了内存回收机制和对象共享机制。

    对象的类型和编码

    redis中的对象定义如下:

    typedef struct redisObject{
    	//类型
    	unsigned type:4;
    	//编码
    	unsigned encoding:4;
    	//指向底层数据结构的指针
    	void *ptr;
    	
    	...
    }
    

    当我们在redis中定义了一个键值对时,我们此时是定义了两个对象,分别是键对象和值对象,在redis中,键对象中是字符串类型的。

    type属性记录了对象的类型,如下表所示:

    当我们对一个键执行type命令时,实际上返回的是值对象的类型:

    type msg
    

    type命令的返回值如下:

    encoding属性记录了对象所使用的编码,也就是对象使用的底层数据结构是什么,这个概念要和数据类型区分开来,因为一个类型的对象可能会有多种不同的编码方式。编码方式如下:

    不同类型和编码的关系如下,每种类型的对象都至少使用了两种不同的编码:

    使用object encoding命令可以查看一个值对象的编码类型:

    object encoding msg
    

    该命令的返回值和编码类型的对应关系如下:

    字符串对象

    字符串对象不仅会单独存在,而且会在其他对象中被引用,字符串对象是redis五种类型的对象中唯一一个会被其他4种类型对象嵌套的对象。

    编码方式

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

    当一个字符串对象保存的是整数值,且这个值可以用long类型来表示时,此时ptr就直接指向一个long类型的整数,此时的编码方式就是int。

    如果一个字符串对象保存的是一个字符串值,且长度大于32字节,那么字符串对象将使用SDS来保存该值,此时的编码方式就是raw。

    如果一个字符串对象保存的是一个字符串值,且长度小于等于32字节,那么字符串对象将使用embstr的方式来保存这个字符串值。embstr编码是一种用于保存短字符串的优化方式,它和SDS一样使用sdshdr类来表示字符串,但是和raw不同的是,embstr将对象和字符串放在一块连续的空间表示,而不是将其分开:

    这种方式使得内存分配和释放变得更方便,而且速度更快。

    值得注意的是,redis在存储小数时会先将其转换为字符串值,然后再保存,使用时也是先取出字符串,然后将其转换为小数。

    编码的转换

    对于int编码的字符串对象,如果我们执行了一些命令将其修改为非整数值,那么它就会转换编码为raw。

    而redis没有设置对embstr的修改程序,embstr编码的字符串是只读的,当我们对这种编码的字符串做出任何修改命令时,都会导致其编码变为raw,然后再执行对应的修改命令。

    字符串命令的实现

    字符串命令在不同编码下的实现如下:

    列表list对象

    编码方式

    对于list类型来说,redis可能使用压缩列表或者双端链表来实现,当元素较少时,使用紧凑的压缩列表速度较快且节约内存;当元素较多时,压缩列表的优势逐渐消失,redis转而使用更适合存储大量数据的双端链表来实现list类型。

    使用压缩列表表示list时,压缩列表的每个entry表示list的一个元素:

    使用链表表示list时,链表的每个节点都表示list的一个元素:

    编码的转换

    当list对象同时满足以下两个条件时就使用ziplist编码,否则就使用linkedlist:

    1、列表对象保存的所有字符串元素的长度都小于64字节

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

    这两个限值可以在配置文件中修改。

    列表命令的实现

    列表命令在不同编码下的实现如下:

    哈希对象

    编码方式

    hash对象的编码方式有ziplist和hashtable两种。

    当hash对象采用压缩列表的形式表示时,插入一个键值对实际上是往压缩列表的表尾方向插入两个entry,保存键的entry在前,保存值的entry在后,具体实现如下图所示:

    当hash对象采用hashtable的形式表示时,类似下图:

    编码的转换

    当哈希对象同时满足以下两个条件时,就会使用ziplist编码,否则使用hashtable:

    1、哈希对象保存的键值对的键和值的字符串长度都小于64字节。

    2、哈希对象保存的键值对数量小于512个。

    这两个限值可以在配置文件中修改。

    哈希命令的实现

    哈希命令在不同编码下的实现如下:

    集合set对象

    编码方式

    set对象的编码方式有两种:intset和hashtable。

    当set对象使用intset表示时,存储set中元素的就是intset的底层数组:

    当set对象使用hashtable表示时,字典的每个哈希节点的键就是set中元素的值,哈希节点的值全部置为null:

    编码的转换

    当同时满足以下两个条件时,set对象使用intset存储,否则就使用hashtable:

    1、set中保存的元素都是整数值。

    2、set保存的元素数量不超过512个。(这个参数可以在配置文件中调整)

    集合命令的实现

    集合命令在不同编码下的实现:

    有序集合sorted set对象

    编码方式

    sorted set对象的编码方式有两种,分别是ziplist和skiplist。

    当有序集合用ziplist来实现时,压缩列表用两个相邻的entry来表示成员member和分值score,且列表内的结合元素按分值从小到大进行排序,分值较小的被放置到靠近表头的方向:

    当有序集合用skiplist来实现时,底层同时包含一个跳表和一个字典:

    typedef struct zset{
    	zskiplist *zsl;
    	dict *dict;
    } zset;
    

    跳表中的每个节点都保存了数据,其中节点的object属性保存了成员member,节点的score属性保存了分值,跳表主要用来完成范围型操作。字典的作用是建立成员member到分值score的映射,字典节点的键保存了元素的成员,值保存了元素的分值。

    有序集合的skiplist编码之所以同时采取了两种底层表示方法,原因是redis要保持高效的操作,采取两种表示方法可以兼顾两者的优点,跳表适合执行范围型操作,而字典则用来支持根据成员查找分值,两种结构缺一不可,表示方法大致如下:

    这里的底层虽然采取了两种结构,但是字典和跳表会共享元素的成员的分值,不会造成内存浪费。

    编码的转换

    当满足以下两个条件时,有序集合会采用ziplist编码,否则使用skiplist。

    1、有序集合保存的元素数量小于128个。

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

    有序集合命令的实现

    有序集合命令在不同编码下的实现:

    类型检查和多态命令

    redis中的操作键的基本命令分为两种,第一种是可以对任何键都执行的命令,如del、rename等;第二种是只能对某一特定类型的键执行的命令,如set、get、hset、hget等。

    在执行特定命令时需要进行类型检查,类型检查是通过redisObject类的type属性来实现的,如果检查合格则执行,如果不合格则报错。

    在执行命令时涉及多态的概念,这里的多态有两个层次,第一个层次是命令可以处理不同类型的键,第二个层次是对于一个类型,命令可以根据编码的不同调整执行方式,类型和编码分别根据redisObject类的type和encoding属性来检查,如llen命令的执行:

    内存回收和对象共享

    redis建立了一个引用计数机制来完成内存回收。在redisObject类中有一个int属性refcount,它代表了引用当前对象的计数值,当创建一个新对象时,该值被置为1,当该值变为0时,对象所占用的内存会被释放。

    redis通过refcount属性来完成对象共享功能,例如,如果同时让键A和键B都指向值为100的字符串对象,那么该字符串对象的refcount就是2,这样只要值一样,redis中只需要保存一份对象内存,只需要改变对象的refcount属性就行了。我们可以用object refcount命令来查看引用计数:

    object refcount A
    

    如果我们在检查引用计数之前设置A的值:

    set A 100
    

    那么引用计数则会返回2,这是因为除了键A以外,服务器程序也持有了该值对象。

    目前redis会在初始化服务器时直接创建了0到9999的1万个字符串对象,当需要用到的时候直接使用这些共享对象,而不需要创建。(初始化创建共享字符串对象的数量可以通过配置文件来修改)

    要注意的是redis目前只对字符串类型的对象实行共享操作,一些更复杂的对象因为检查是否相同消耗的时间太长,所以redis放弃了其他类型的对象共享。

    对象的空转时长

    redisObject有一个unsigned类型的属性lru,该属性记录了对象最后一次被命令程序访问的时间,用object idletime这个命令可以返回某个键的空转时长,空转时长等于当前时间减去键对象的lru的值。(object idletime命令不会刷新lru)

    空转时长的重要应用是在redis的内存淘汰策略中被使用,有时空转时间较高的那部分键会优先被服务器释放。

  • 相关阅读:
    洛谷—— P3353 在你窗外闪耀的星星
    洛谷—— P1238 走迷宫
    洛谷—— P1262 间谍网络
    9.8——模拟赛
    洛谷—— P1189 SEARCH
    算法
    May 22nd 2017 Week 21st Monday
    May 21st 2017 Week 21st Sunday
    May 20th 2017 Week 20th Saturday
    May 19th 2017 Week 20th Friday
  • 原文地址:https://www.cnblogs.com/yinyunmoyi/p/11522118.html
Copyright © 2011-2022 走看看