Redis的数据结构与对象——对象

前言:Reids并没有直接使用这些数据结构来实现数据库的键值对,而是基于这些数据结构创建了一个对象系统,这个对象系统包括字符串对象、列表对象、哈希对象、集合对象、有序集合对象,每种对象起码用到了两种上述的数据结构。这样的好处是:

1、根据不同的对象类型,可以在redis命令执行之前判断这个对象是否可以执行相关的命令。

2、针对不同的使用场景为对象设置不同的数据结构实现,从而优化对象在不同使用场景下的效率,可以兼顾内存和时间复杂度。

一、对象的实现

    redis使用对象来表示数据库中的键值对,当创建一个键值对的时候,一定会创建两个对象,键一定是字符串对象,值会是五种对象的其中之一。

struct redisObject {

    unsigned type:4; // 类型

    unsigned encoding:4; // 编码 

    void *ptr; //执行底层实现数据结构的指针

    int refcount; //引用计数,用于内存回收

    unsigned lru:22; // 记录最近一次访问这个对象的时间

}

1.1、 type类型

    type属性记录了对象的类型,可以使用TYPE命令返回数据库键的类型(ps:如果键值对中值是列表对象,一般称之为列表键,TYPE命令返回list)。

    type属性包括:


type类型

   1.2 、encoding编码

    encoding属性记录了对象使用了怎样的编码,也就是说这个对象使用了什么数据结构作为对象的底层实现。使用OBJECT ENCODING命令可返回这个对象的编码方式。

    encoding属性包括:

#define REDIS_ENCODING_INT   // 编码为long类型整数

#define REDIS_ENCODING_EMBSTR // 编码为embstr编码的SDS

#define REDIS_ENCODING_RAW  // 编码为SDS

#define REDIS_ENCODING_HT  // 编码为字典

#define REDIS_ENCODING_LINKEDLIST  // 编码为双端链表

#define REDIS_ENCODING_ZIPLIST // 编码为压缩列表

#define REDIS_ENCODING_INTSET  // 编码为整数集合

#define REDIS_ENCODING_SKIPLIST  // 编码为跳跃表和字典

    通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联只一种编码,极大的提高了redis的灵活性和效率,因为redis可以根据不同的使用场景来为对象设置不同的编码,从而优化对象在某一场景下的使用效率。比如:

    redis使用压缩列表作为列表键的底层实现之一。

    1、压缩列表比双端链表更节约内存,在元素数量较少的时候,在内存中一连续内存块方式保存的压缩列表会比双端链表更快被载入到内存中。

    2、随着列表对象包含的数量越来越多,压缩列表的优势越来越低,从而将列表对象的实现从压缩列表变为功能更强、更适合保存大量元素的双端链表。

  1.3、type的不同编码




二、字符串对象

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

2.1、int

    如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int 。   

    相对于SDS优势在于 :1、节省内存 ;2、对于整数值的字符串对象可能会被执行INCR操作,SDS需要先将字符串转成整形,再执行加减操作,再将结果转成字符串保存,如果底层保存一个整形变量就不需要做类型转换了。

2.2、embstr

   如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度小于等于39字节, 那么字符串对象将使用embstr编码的方式来保存这个字符串值

2.3、SDS

    如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度大于39字节, 那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 并将对象的编码设置为raw。

2.4、embstr与sdshdr区别

    embstr编码是专门用于保存短字符串的一种优化编码方式, 这种编码和raw编码一样, 都使用redisObject结构和sdshdr结构来表示字符串对象。

    raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构, 而embstr编码则通过调用一次内存分配函数来分配一块连续的空间, 空间中依次包含redisObject和sdshdr两个结构、

    redisObject | SDS     连续内存块

    embstr 有以下好处:

    1、embstr 编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次

    2、释放 embstr 编码的字符串对象只需要调用一次内存释放函数, 而释放 raw 编码的字符串对象需要调用两次内存释放函数

因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面, 所以这种编码的字符串对象比起 raw 编码的字符串对象能够更好地利用缓存带来的优势

    番位:为什么是39?


为什么是39?


三、列表对象

    列表的编码encoding可以是压缩列表或者双向链表。

3.1、编码转换

当同时满足以下两个条件的时候,列表对象使用压缩列表来编码,否则使用双端链表。

1、所有字符串对象的元素的长度都小于64字节;

2、元素数量不超过512个。    

ps:其实可以通过配置文件修改。list-max-ziplist-value、list-max-ziplist-entries。


四、哈希对象

哈希对象可以使用压缩列表和字典来实现。

4.1、压缩列表的实现

    每当有新的键值对加入到哈希对象的时候,程序会先将键的压缩列表节点推入到压缩列表的表尾,然后再将值的压缩列表节点保存到要锁列表的表尾。所以:

1、同一个键值对一定是紧靠在一起的;

2、先添加到哈希对象的键值对会放在表头位置,后来添加的会被放在表尾位置。

4.2、字典的实现

另外,使用字典的时候,键值对的键和值都是字符串对象。说明,redis的哈希对象只会使用字符串,只保存字符串。

4.3、编码转换

当同时满足以下两个条件的时候,哈希对象使用压缩列表来编码,否则使用字典。

1、所有键值对的键和值的字符串长度都小于64字节;

2、键值对数量不超过512个。    

ps:其实可以通过配置文件修改。hash-max-ziplist-value、hash-max-ziplist-entries。


五、集合对象

    集合对象的编码可以是整数集合,也可以是字典。

    当使用字典的时候,字典的每个键是一个字符串对象(这个字符串对象就是一个集合元素),字典的值被设置为null。

 5.1、编码转换

当同时满足以下两个条件的时候,集合对象使用整数集合来编码,否则使用字典。    

1、所有元素都是整数值;

2、元素数量不超过512个。    

ps:第二个选项其实是可以修改的,set-max-intset-entries。


六、有序集合

    有序集合的实现有两种,一种是压缩列表,一种是字典+跳跃表。

6.1、压缩列表    

    使用压缩列表的时候,每个集合元素都使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二个节点保存元素的分值(score)。

    压缩列表的集合元素按分值的大小从小到大排列。

 6.2、skipList实现

    skipList编码的有序集合使用zset作为底层实现,一个zset包括一个字典和一个跳跃表。

struct zset {

    zskiplist *zsl;

    dict *dict;

}

    zset中的zsl跳跃表,按分值的从小到大保存了所有的集合元素,每个跳跃表节点都保存了一个集合元素。通过跳跃表ZRANGE、ZRANK都是基于跳跃表API实现的。

    zset中的dict字典为有序集合创建了,一个从成员到分值的映射,字典的每个键值对都保存了一个集合元素,字典的键是member,字典的值是score。通过字典,程序可以O(1)查找元素的分值,ZSCORE就是基于这个实现的。

    另外,有序集合的每一个元素的member都是字符串对象,每一个元素的score都是一个double类型的浮点数

    值得一提的是,这些字符串对象和浮点数,字典和跳跃表都会通过指针来共享,不会因此而浪费内存。



番外:为什么需要同时使用字典和跳跃表?

 原因:按道理来说,只要使用一种数据结构就足以了,但是redis使用了两种。

因为如果仅使用字典,字典是无序保存集合元素的,那么ZRANGE等操作,就要对所有元素进行排序,时间复杂度O(NlogN),额外的空间复杂度O(N),因为要创建一个额外的数组来保存排序好的元素。

如果仅使用跳跃表,ZSCORE就从O(1)上升到跳跃表的查询时间复杂度O(logN)了。

为了尽量快速执行,redis同时使用了这两种数据结构。

6.3、编码转换

当同时满足以下两个条件的时候,有序集合对象使用压缩列表来编码,否则使用skiplist。

1、所有元素的成员member的字符串长度都小于64字节;

2、元素数量不超过128个。    

ps:其实可以通过配置文件修改。zset-max-ziplist-value、zset-max-ziplist-entries。


七、类型检查和命令多态

redis的命令大致分为两种,一种是可以对任何类型的键执行,一种只能对指定类型的键执行。

任意类型的键可执行的命令包括DEL、EXPIRE等等。

而有些命令只能对指令类型的键使用,比如SET、HSET、RPUSH、SADD、ZADD。

7.1、类型检查

执行命令之前,redis会先查看这个对象type,然后决定是否执行,如果不能执行则返回一个错误

7.2、多态命令

redis还会根据对象的encoding,选择正确的命令实现来执行命令。

比如列表对象的实现有压缩列表和双端链表。redis会根据encoding选择不同的函数来实现指定的命令。

7.3、举例




八、内存回收

C语言并没有自动内存回收功能,所以redis是使用引用计数的方法构建自己的内存回收机制。对应的字段是refcount。

OBJECT REFCOUNT (键) 可以返回这个就这个键的值对象,这个值对象一共被引用了多少次。


九、内存共享

refcount除了可以用于内存回收,还用于内存共享。

在redis中,为了节约内存,可能会共享一些对象。包括两个步骤:

 1、数据库见的值指针都指向同一个对象

  2、这个对象的计数器加1


十、对象的空转时常

        lru属性记录了最近一次被命令程序访问的时间,是个时间戳。

        OBJECT IDLETIME可以打印出这个键的空转时常,就是当前时间减去键的值对象的lru时间计算出来的。

        lru和redis的内存淘汰机制关联很大,allkeys-lru策略等等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 转载:可能是目前最详细的Redis内存模型及应用解读 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据...
    jwnba24阅读 620评论 0 4
  • 前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站...
    小陈阿飞阅读 802评论 0 1
  • 对象 当称呼一个数据库键为"字符串键"、"列表键"时,指的是这个键对应的值为"字符串对象"、"列表对象"。 Red...
    xMustang阅读 256评论 0 0
  • 01 你小时候,有没有被老师,爸妈翻过日记本的经历? 最近在知乎上看到这样一件事情。一个学校的家长群中,老师发了一...
    元気派阅读 586评论 0 1
  • 春花细柳,为谁病染相思瘦, 夏藕立杨,望尔健身高举头。 秋枫菊友,紫山金圃共斟酒, 冬柏梅羞,白雪红梅两畅游!
    云逸1108阅读 67评论 0 0