Redis原码阅读--Object(101)

对象 Redis基于前面的那些数据结构,创建了一个对象系统用来实现键值对数据库。
那么就形成了五大基础对象

  1. 字符串对象 String
  2. 列表对象 List
  3. 哈希对象 Hash
  4. 集合对象 Set
  5. 有序集合对象 ZSet
因为引入了对象,所有Redis实现了基于引用计数技术的内存回收机制,当程序不在使用某个对象的时候,对象所占用的内存就会被自动释放,另外还实现了对象共享机制,在某些条件下多个数据库可以共享一个对象来节约内存。
类型与编码 Redis使用对象表示数据库中的键值,那么我们创建一个键值对,我们至少会创建两个对象
/* * Redis 对象 */ #define REDIS_LRU_BITS 24 #define REDIS_LRU_CLOCK_MAX ((1<lru */ #define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */ typedef struct redisObject {// 类型 unsigned type:4; // 编码 unsigned encoding:4; // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */// 引用计数 int refcount; // 指向实际值的指针 void *ptr; } robj;

对象的ptr指针指向对象的底层实现数据结构,它是由encoding决定的
TYPE 键名 可以查看键对应的值对象类型 set msg "hello" type msgstringrpush numbers 1 3 5 type numberslist

Redis原码阅读--Object(101)
文章图片

字符串对象 字符串对象的编码可以是 int,raw,embstr
如果一个字符串对象保存的是整数值没并且这个整数值可以用long表示,那么字符串对象会将整数值保存在字符串对象结构的ptr里(将void* 转换为long)并且将字符串编码设置为int
如果字符串对象保存一个字符串值,并且字符串长度大于32字节,那么使用的SDS,编码设置为raw
如果字符串对象保存一个字符串值,并且字符串长度小于32字节,编码设置为embstr
embstr是专门保存短字符串的一种优化编码,它和raw一样都是使用redisObject结构和sdshdr结构来表示字符串对象,当时raw会调用两次内存分配函数来分别创建redisObject和sdshdr,而embstr会通过一次内存分配来创建一块连续的空间,里面放redisObject和sdshdr 效果时一样的只是过程简单了。
那么embstr的优势是
  1. 创建对象只需要分配一次内存
  2. 释放对象也只需要调用一次释放函数
  3. embstr分配的内存是连续的
如果你要存一个浮点数 那么程序会先将浮点数转换为字符串,然后保存的是字符串的值
编码转换 【Redis原码阅读--Object(101)】int和embstr对象在某些条件下会被转换为raw
对应int,如果我们向对象执行了一些命令,使得之歌对象保存的不再是整数,而是一个字符串,那么int会变为raw
比如向一个整数追加一个字符串
set number 10086 append number "hello" object encoding number raw

redis并没有定义对embstr对象的API,所有其实上embstr字符串是只读的,当我们实际对embstr字符串操作时,就会自动变raw
列表对象 编码可以时ziplist或者linkedlist
Redis原码阅读--Object(101)
文章图片

编码转换 什么时候使用ziplist 同时满足
  1. 列表对象保存的所有字符串元素长度都小于64字节
  2. 列表对象保存的元素数量小于512,
不满足这两个就用linkedlist
哈希对象 编码可以是ziplist或者hashtable
1、如果是ziplist会先push入键再push入值 都是在表尾 他们总是挨在一起的
2、如果是hashtable,那么hash对象中的每个键值对都是一个字典键值对保存的,
字典中键和值都是一个字符串对象
什么时候使用ziplist 同时满足才行
  1. 哈希对象保存的所有键和值的字符串元素长度都小于64字节
  2. 哈希对象保存的元素数量小于512,
不满足这两个就用hashtable
集合对象 集合对象可以是intset或者hashtable
如果集合里的值都是整数就用intset
Redis原码阅读--Object(101)
文章图片

什么时候使用intset 同时满足才行
  1. 集合对象保存的所有元素都是整数
  2. 集合对象保存的元素数量小于512,
有序集合 ziplist或者skiplist
1、ziplist,每个集合元素使用两个紧挨着的压缩列表节点保存,第一节点保存成员,第二个元素保存分值
2、skiplist使用的是一个字典和跳跃表,跳跃表按分值从小到大保存所有集合元素,每个跳跃表节点保存一个集合元素,dict字典为有序集合创建了一个从成员到分值的映射,字典键保存元素成员,字典值保存元素分值。
/* * 有序集合 */ typedef struct zset {// 字典,键为成员,值为分值 // 用于支持 O(1) 复杂度的按成员取分值操作 dict *dict; // 跳跃表,按分值排序成员 // 用于支持平均复杂度为 O(log N) 的按分值定位成员操作 // 以及范围操作 zskiplist *zsl; } zset;

Redis原码阅读--Object(101)
文章图片

什么时候用ziplist 同时满足
  1. 有序集合保存的元素数量小于128个
  2. 有序集合保存的所有元素成员的长度都小于64个字节
类型检查和命令多态 Redis中用于操作键的命令可以分为两种
1、可以对任意类型的键执行,DEL,TYPE ,OBJECT等
2、对象的专用命令,set(字符串),hset(哈希),rpush(列表),sadd(集合),zadd(有序集合)。
那么问题就来了,在执行一个类型特定命令之前,redis必须要先检查类型,然后再决定是否执行
多态命令的实现 redis还会根据值对象的编码没选择正确的命令去执行。
现在,考虑这样一个情况,如果我们对一个键执行LLEN命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN命令实现:如果列表对象的编码为ziplist,那么说明列表对象的实现为压缩列表,程序将使 用ziplistLen函数来返回列表的长度; 如果列表对象的编码为linkedlist,那么说明列表对象的实现为双端链表,程序将使用listLength函数来返回双端链表的长度;

内存回收 总所周知C不没有垃圾回收器的,redis用一个引用技术技术实现的内存回收,程序跟踪对象的引用技术信息,再合适的时候自动释放内存
对象的引用计数信息会随着对象的使用状态而不断变化:
  • 在创建一个新对象时,引用计数的值会被初始化为1;
  • 当对象被一个新程序使用时,它的引用计数值会被增一;
  • 当对象不再被一个程序使用时,它的引用计数值会被减一;
  • 当对象的引用计数值变为0时,对象所占用的内存会被释放。
对象共享 引用计数属性还带来了对象共享的作用,因为当对象被一个新程序使用时,它的引用计数值会被增一。
redis让多个键共享一个值对象
  1. 将数据库键的值指针指向一个现有的值对象
  2. 将被共享的值对象的引用计数加一
注意 Rdis再初始化服务器时,创建了一万个字符串对象,这些对象包含了0到9999的所有整数值,当需要用到时服务器就会使用共享对象,而不是新创建对象。
理论上所有对象都时可以共享的,当时再共享之前需要确认两个需要共享的元素是不是类型相同,时很好cup时间的,所有redis只包含了整数和字符串对象的共享。
对象的空转时长
// 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ object tdletime 键 可以打印lru

如果服务器开启了maxmemory选项,并且内存回收算法是 volatile-iru或者 allkeys-lru,那么服务器内存占用超过maxmemory时,空转时间长的会先被释放回收。

    推荐阅读