背景知识:

Redis并没有直接使用sds,双端链表,字典,压缩列表,跳表等这些数据结构来直接实现键值对数据库,而是基于这些对象创建了一个对象系统,这个对象系统包含5个对象:字符串对象,列表对象,哈希对象,集合对象和有序集合对象,字符串对象是唯一会被其他四种对象嵌套的对象

1.我们可以针对不同的使用场景,为对象设置多种不同的数据结构,从而优化对象在不同场景下的使用效率

2.Redis的对象系统实现了基于引用计数的内存回收机制

3.Redis的对象系统还实现了对象共享机制,这个机制在适当条件下,通过让多个数据库键共享同一个对象来节约内存

4.Redis的对象带有访问时间记录信息,该信息可以用来计算数据库键的空转时长,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能会被服务器优先删除

5.当我们在redis中创建一个新的键值对时,我们至少会创建两个对象,一个对象用作键,另外一个对象用作值

一.Redis对象系统

typedef struct redisObject {

    // 类型,键值对中值的类型
unsigned type:; // 编码,决定采用什么类型的数据结构实现
unsigned encoding:; // 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ // 引用计数
int refcount; // 指向实际值的指针
void *ptr; } robj;

通过encodeing属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大的提升了Redis的灵活性和效率,因为Redis可以更具不同的使用场景来为对象设置一个不同的编码,从而优化对象在某一场景下的效率

比如下面这种情况:

在列表对象包含较少元素时,Redis使用压缩列表作为列表对象的底层实现,因为压缩列表比起双端列表更节约内存,并且在元素数量较少时,在内存中以连续块的方式保存压缩列表比起双端链表可以更快的载入到缓存中,但是!随着元素的增加,使用压缩列表带来的优势越来越小,对象系统就会将列表的底层实现转向功能更强,也更适合保存大佬元素的双端列表上,其他类型对象也会通过使用多种不同的编码来进行类似的优化

下面是 不同对象类型对应的不同编码:

|-- 1.使用整数值实现的字符串对象
                  |
字符串对象: |-- 2.使用embstr编码的SDS简单动态字符串实现的字符串对象
                  |
                  |-- 3.使用SDS简单动态字符串实现的字符串对象
           
               |--1.使用ziplist压缩列表实现的列表对象
列表对象: |
               |--2.使用list双端链表实现的列表对象        
           
               |--1.使用ziplist压缩列表实现的哈希对象
哈希对象: |
               |--2.使用dict字典实现的哈希对象

|--1.使用intset整数集合实现的集合对象
集合对象: |
               |--2.使用dict字典实现的集合对象
           
                    |--1.使用ziplist压缩列表实现的有序集合对象
有序集合对象:|
                    |--2.使用跳跃表和字典实现的有序集合对象

1.字符串对象

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

1)当字符串对象保存的是整数值时,字符串对象的编码是int

2)当字符串对象保存的是字符串时,并且这个字符串的长度大于39字节,那么字符串对象的编码是raw

3)当字符串对象保存的是字符串时,并且这个字符串的长度小于等于39字节,那么字符串对象的编码是embstr

说明:

embstr编码是专门用于保存短字符串的一种优化编码方式

embstr编码和raw编码的区别:

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

使用embstr编码来保存短字符串值有以下两个好处:

1)调用内存分配函数的次数减少了一次,并且调用内存释放函数的次数也减少了一次

2)embstr编码的字符串对象的所有数据都保存在一块连续的内存中,这样可以更好的利用缓存带来的优势、

ps:浮点类型的数据也是通过字符串值来保存的

编码可以进行特定的相互转换

2.列表对象

1.列表对象的编码可以是ziplist压缩列表或者linkedlist链表

问题:列表对象什么时候采用ziplist压缩列表编码,什么时候采用linkedlist链表编码?

当列表对象满足下面两个条件的时候,采用ziplist压缩列表进行编码

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

2)列表保存的元素数量小于512个

若不能通过满足上面两个条件的话列表将采用双端链表编码

3.哈希对象

哈希对象的编码可以是ziplist压缩列表或hashtable哈希表

当哈希对象采用ziplist压缩列表进行编码的时候:

保存了同一键值对的两个结点总是紧紧挨在一起,保存键的结点在前,保存值的结点在后

当哈希对象满足下面两个条件时,哈希对象采用ziplist压缩列表进行编码:

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

2)噶厦镀锡保存的键值对数量小于512个

当不满足上面两个条件时会进行编码转换采用hashtable进行编码

4.集合对象

集合对象的编码可以是intset整数集合或者hashtable哈希表


当集合对象满足下面两个条件时,集合对象采用intset编码:

1)集合对象保存的所有元素都是整数值

2)集合对象保存的元素数量不超过512个

如果不满足上面两个条件,集合对象将采用hashtable哈希表进行编码

5.有序集合对象

有序集合对象的编码可以是ziplist或者skiplist

ziplist编码:ziplist结构

skiplist编码:skiplist结构和dict结构

当有序集合对象满足下面两个条件时,有序集合对象采用ziplist编码

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

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

否则的话,有序集合对象将采取skiplist编码,底层用skiplist和dict数据结构实现

关于有序集合对象采用skiplist编码的说明:

在采用skiplist编码的时候,其底层采用的是skiplist跳表和dict字典

1.通过跳跃表,有序集合可以高效的进行范围性操作

2.通过字典,有序集合可以高效的查找成员到分值的映射

值得一提的是,虽然有序集合对象在采用skiplist编码的时候,同时采用了跳跃表和字典来保存原始,但是着两种数据结构都会通过指针来共享相同原始的成员和分值,不会因此而浪费额外的内存来保存相同元素

为什么有序集合在采用skiplist编码的时候,需要通过采用跳表和字典来进行底层的实现?

1)采用字典,不采用跳表:虽然查找成员到分值的映射的复杂度为O(1),但是执行比如ZRANK命令时,程序需要对字典保存的所有元素进行排序,至少需要O(log N),以及额外的O(N)内存空间

2)采用跳表,不采用字典:虽然执行范围性操作的时间复杂度是O(log N),但是根据成员查找映射的分值的操作的复杂度将从O(1)上升到O(log N)

所以为了让有序集合的查找和范围性操作都尽可能的快速执行,Redis选择同时使用字典和跳跃表两种数据结构来实现有序集合,同时有序集合中的字典和跳跃表会共享元素的成员和分值,所以并不会造成数据重复,也不会因此浪费任何内存

Redis的内存回收

1.Redis采用引用计数法进行内存回收,该方法最大的问题就是不能解决互相引用的问题

2.对象的空转时长:当前时间减去该对象最后一次被访问的时间就是该对象的空转时长,空转时长较长的对象在内存不够时会被回收

Redis的对象共享

采用对象共享机制是为了节约内存,数据库保存的相同值的对象越多,对象共享机制就节约越多的内存

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

需要注意的是

尽管共享复杂的对象可以节约更多的内存,但是复杂的对象比较起来也比较耗时间,比如如果共享的对象包含了多个值的对象,那么比较两个对象的复杂度为O(N*N),这个比较操作非常耗费CPU时间,所以收到CPU时间的现在,Redis只多包含整数值的字符串对象进行共享

总结:

1)Redis数据节中每个键值对的键和值都是一个对象

2)Redis共有字符串对象,列表对象,哈希对象,集合对象,有序集合对象,这五种对象,每种类型的对象至少都有两种以上的编码方式,不同的编码方式可以在不同的使用场景上使用,可以优化对象的使用效率

3)服务器在执行某些命令前,需要先检查给定的键是否可以执行指定的命令,而检查一个键的类型就是检查键对应的值的对象类型

 

Redis学习之对象系统源码分析的更多相关文章

  1. Redis学习之底层链表源码分析

    Redis底层链表的源码分析: 一.链表结点的结构(单个结点): // listNode 双端链表节点 typedef struct listNode { // 前置节点 struct listNod ...

  2. Android12系统源码分析:NativeTombstoneManager

    Android12系统源码分析:NativeTombstoneManager 概述 android12新增的system_server进程(LocalService)本地服务,用于管理native t ...

  3. memcached学习笔记——存储命令源码分析下篇

    上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...

  4. memcached学习笔记——存储命令源码分析上篇

    原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...

  5. Redis 专栏(使用介绍、源码分析、常见问题...)

    一.介绍相关 说Redis : 介绍Redis特性,使用场景,使用Jedis操作Redis等. 二.源码分析 1. 数据结构 Redis源码分析(sds):Redis自己封装的C语言字符串类型. Re ...

  6. Vue3中的响应式对象Reactive源码分析

    Vue3中的响应式对象Reactive源码分析 ReactiveEffect.js 中的 trackEffects函数 及 ReactiveEffect类 在Ref随笔中已经介绍,在本文中不做赘述 本 ...

  7. tornado 学习笔记6 Application 源码分析

    Application 是Tornado重要的模块之一,主要是配置访问路由表及其他应用参数的设置. 源代码位于虚拟运行环境文件夹下(我的是env),具体位置为env > lib>sit-p ...

  8. jQuery Deferred对象详细源码分析(-)

    本系列文章讲介绍这个Deferred东西到底拿来干什么,从1.5版本加进来,jQuery的很多代码都重写了.直接先上源码分析了,清楚了源码分析,下节将讲具体的应用 以及应用场景. 创建对象 var d ...

  9. Android FrameWork 学习之Android 系统源码调试

    这是很久以前访问掘金的时候 无意间看到的一个关于Android的文章,作者更细心,分阶段的将学习步骤记录在自己博客中,我觉得很有用,想作为分享同时也是留下自己知识的一些欠缺收藏起来,今后做项目的时候会 ...

随机推荐

  1. 如何使用Charles让手机访问PC自定义域名?

    需求:移动端访问PC上的自定义域名,如在Nginx上配置的域名 ​ 如vv.zzcloud.com这个域名在pc上是通过host映射的方式访问,现在需要在手机上访问到这个域名. 工具:Charles代 ...

  2. MTSC2019大会日程重磅发布,腾讯WeTest独家Topic大揭秘!

    WeTest 导读 中国移动互联网测试开发大会 Mobile Testing Summit China(简称 MTSC)是由国内最大的移动测试技术社区 TesterHome 发起的软件测试行业技术会议 ...

  3. 利用Python模拟登录pastebin.com

    任务 在https://pastebin.com网站注册一个账号,利用python实现用户的自动登录和创建paste.该任务需要分成如下两步利用python实现: 账号的自动登录 paste的自动创建 ...

  4. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  5. X264-编码模块和NAL打包输出

    在上一篇介绍了编码器的VCL编码操作,分析了函数x264_slice_write().函数x264_slice_write()里有四个关键模块,分别是宏块分析模块.宏块编码模块.熵编码模块和滤波模块, ...

  6. Python从零开始——列表List

    一:Python列表知识总览 二:列表操作符 三:Python内置函数操作列表 四:Python列表封装函数

  7. 每次都能让人头大的 Shader -- 从一次简单的功能说起

    最近有个功能, 要渲染从主相机视角看到的另一个相机的可视范围和不可见范围, 大概如下图 : 简单来说就是主相机视野和观察者相机视野重合的地方, 能标记出观察者相机的可见和不可见, 实现原理就跟 Sha ...

  8. 08配置基础路径 同时导出一个函数和一个变量 封装微信请求Api

    地址===>https://www.bilibili.com/video/av58993009/?p=46 1==>配置基础路径同时导出一个函数和一个变量 var mynetwork= f ...

  9. sikuli 搜索例子

    #coding:utf-8kw = input(u"请输入您要搜索的关键字:")#openAPP('‪C:\Users\ceshi\AppData\Local\Google\Chr ...

  10. NeuHub图像垃圾分类api和百度图像识别api

    京东 NeuHub图像垃圾分类申请:http://neuhub.jd.com/gwtest/init/242 文档:https://aidoc.jd.com/image/garbageClassifi ...