「Redis源码解读」—数据结构(六)对象

知识点

  • redis数据库中的每一个键值对的键和值都是一个对象
  • redis共有字符串、列表、哈希、集合、有序集合五种类型的对象,每种类型的对象至少都有两种或以上的编码方式,不同编码可以在不同的使用场景上优化对象的使用效率
  • redis在执行命令之前,会先检查给定键的类型是否能执行指定命令,而检查一个键的类型就是检查键的值对象的类型
  • redis的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,该对象所占用的内存就会被自动释放
  • redis会共享值为0-9999的字符串对象
  • 对象会记录自己的最后一次被访问的时间,这个时间可以用于计算对象的空转时间。

结构体

typedef struct redisObject {  
    // 类型  
    unsigned type:4;          
    // 不使用(对齐位)  
    unsigned notused:2;  
    // 编码方式  
    unsigned encoding:4;  
    // LRU 时间(相对于 server.lruclock)  
    unsigned lru:22;  
    // 引用计数  
    int refcount;  
    // 指向对象的值  
    void *ptr;  
} robj;
  • 编码常量 编码所对应的底层数据结构
    REDIS_ENCODING_INT —— long 类型的整数
    REDIS_ENCODING_EMBSTR —— embstr 编码的简单动态字符串(SDS)
    REDIS_ENCODING_RAW ——简单动态字符串
    REDIS_ENCODING_HT ——字典
    REDIS_ENCODING_LINKEDLIST ——双端链表
    REDIS_ENCODING_ZIPLIST ——压缩列表
    REDIS_ENCODING_INTSET—— 整数集合
    REDIS_ENCODING_SKIPLIST ——跳跃表

字符串对象

字符串对象的编码可以是int、raw或者embstr。
如果一个字符串的内容可以转换为long,那么该字符串就会被转换成为long类型,对象的ptr就会指向该long,并且编码类型也用int类型表示。
普通的字符串有两种,embstr和raw。embstr编码是由redIsObject和sdshdr组成,sdshdr结构体如下

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
}

redIsObject占用16字节,如果buf长度是39个字节,那么sdshdr就是8+39+1=48个字节,那么embstr就是64字节,而redis采用的是jemalloc内存分配器,可以分配8,16,32,64字节等大小的内存,而sdshdr最小分配8+8+1=17字节,那么embstr最小就是33字节,需要分配64字节。所以对于redis来说小于等于39字节的字符串采用embstr编码,大于则用raw编码。

  • embstr好处:
    1.embstr的创建只需分配一次内存,而raw为两次(一次为sds分配对象,另一次为objet分配对象,embstr省去了第一次,直接分配一块连续的内存)。
    相对地,释放内存的次数也由两次变为一次。
    2.embstr的objet和sds放在一起,更好地利用缓存带来的优势。
    3.释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要条用两次内存释放函数
    4.因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所有这种编码的字符串对象比起raw编码的字符串对象能够更好的利用缓存带来的优势。
    需要注意的是,redis并未提供任何修改embstr的方式,即embstr是只读的形式。对embstr的修改实际上是先转换为raw再进行修改。

列表对象

  • 列表对象的编码可以是ziplist或者linkedlist。

ziplist是一种压缩链表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。

另一方面,linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点Node都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素

编码转换

当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码
1.列表对象保存的所有字符串元素的长度都小于64字节
2.列表对象保存的元素数量小于512个,不能满足这两个条件的列表对象需要使用linkedlist编码

哈希对象

  • 哈希对象的底层实现可以是ziplist或者hashtable。

ziplist中的哈希对象是按照key1,value1,key2,value2这样的顺序存放来存储的。当对象数目不多且内容不大时,这种方式效率是很高的。

编码转换

当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码
1.哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
2.哈希对象保存的键值对数量小于512个,不能满足这两个条件的哈希对象需要使用hashtable编码

集合对象

  • 集合对象的编码可以是intset或者hashtable。
    intset是一个整数集合,里面存的为某种同一类型的整数,支持如下三种长度的整数:
#define INTSET_ENC_INT16 (sizeof(int16_t))  #define INTSET_ENC_INT32 (sizeof(int32_t))  #define INTSET_ENC_INT64 (sizeof(int64_t))  

intset是一个有序集合,查找元素的复杂度为O(logN),但插入时不一定为O(logN),因为有可能涉及到升级操作。比如当集合里全是int16_t型的整数,这时要插入一个int32_t,那么为了维持集合中数据类型的一致,那么所有的数据都会被转换成int32_t类型,涉及到内存的重新分配,这时插入的复杂度就为O(N)了。是intset不支持降级操作。

编码转换

当集合对象可以同时满足以下两个条件时,集合对象使用intset编码
1.集合对象保存的所有元素都是整数值
2.集合对象保存的元素不超过512,不能满足这两个条件的哈希对象需要使用hashtable编码

有序集合对象

  • 有序集合的编码可能两种,一种是ziplist,另一种是skiplist
    ziplist作为集合和作为哈希对象是一样的,member和score顺序存放。按照score从小到大顺序排列。它的结构不再复述。
    skiplist是一种跳跃表,它实现了有序集合中的快速查找,在大多数情况下它的速度都可以和平衡树差不多。但它的实现比较简单,可以作为平衡树的替代品。它的结构比较特殊。
编码转换

1.有序集合保存的元素数量小于128个
2.有序集合保存的所有元素成员的长度都小于64字节
不能满足以上两个条件的有序集合对象将使用skiplist编码

内存回收

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

  • 对象的引用计数信息会随着对象的使用状态而不断变化
    1.在创建一个新对象时,引用计数的值会被初始化为-1;
    2.当对象被一个新程序使用时,它的引用计数器会被+1;
    3.当对象不再被一个程序使用时,它的引用计数值会被-1;
    4.当对象的引用计数值变为0时,对象所占用的内存会被释放;

对象共享

假设键A于键B都创建了一个包含整数100的字符串对象作为值对象时,redis将会通过让键A和键B共享同一个字符串对象节约内存。

  • 在redis中让多个键共享一个值对象需要执行以下两个步骤:
    1.将数据库键的值指针指向一个现有对象;
    2.将被共享的值对象的引用计数器增1;

对象空转时长

redisObject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间;
如果redis打开了maxmemory选项,那么当redis占用的内存数超过了maxmemory选项所设置的上限时,空转时长较高的那部分键会被优先被释放,从而回收内存

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

推荐阅读更多精彩内容

  • Redis的内存优化 声明:本文内容来自《Redis开发与运维》一书第八章,如转载请声明。 Redis所有的数据都...
    meng_philip123阅读 18,873评论 2 29
  • 参考来源 Redis的内存优化 Redis所有的数据都在内存中,而内存又是非常宝贵的资源。对于如何优化内存使用一直...
    秦汉邮侠阅读 1,280评论 0 2
  • 转载:可能是目前最详细的Redis内存模型及应用解读 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据...
    meng_philip123阅读 1,424评论 1 22
  • 转载:可能是目前最详细的Redis内存模型及应用解读 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据...
    jwnba24阅读 620评论 0 4
  • 高考与上海这座国际化大都市失之交臂,去了江苏。大学时就立下志向毕业去上海工作,10年校园招聘只投上海企业简历而且H...
    DennisFly阅读 2,850评论 1 0