Redis原码阅读--Object(101)
对象
Redis基于前面的那些数据结构,创建了一个对象系统用来实现键值对数据库。
那么就形成了五大基础对象
- 字符串对象 String
- 列表对象 List
- 哈希对象 Hash
- 集合对象 Set
- 有序集合对象 ZSet
类型与编码 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
文章图片
字符串对象 字符串对象的编码可以是 int,raw,embstr
如果一个字符串对象保存的是整数值没并且这个整数值可以用long表示,那么字符串对象会将整数值保存在字符串对象结构的ptr里(将void* 转换为long)并且将字符串编码设置为int
如果字符串对象保存一个字符串值,并且字符串长度大于32字节,那么使用的SDS,编码设置为raw
如果字符串对象保存一个字符串值,并且字符串长度小于32字节,编码设置为embstr
embstr是专门保存短字符串的一种优化编码,它和raw一样都是使用redisObject结构和sdshdr结构来表示字符串对象,当时raw会调用两次内存分配函数来分别创建redisObject和sdshdr,而embstr会通过一次内存分配来创建一块连续的空间,里面放redisObject和sdshdr 效果时一样的只是过程简单了。
那么embstr的优势是
- 创建对象只需要分配一次内存
- 释放对象也只需要调用一次释放函数
- 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
文章图片
编码转换 什么时候使用ziplist 同时满足
- 列表对象保存的所有字符串元素长度都小于64字节
- 列表对象保存的元素数量小于512,
哈希对象 编码可以是ziplist或者hashtable
1、如果是ziplist会先push入键再push入值 都是在表尾 他们总是挨在一起的
2、如果是hashtable,那么hash对象中的每个键值对都是一个字典键值对保存的,
字典中键和值都是一个字符串对象
什么时候使用ziplist 同时满足才行
- 哈希对象保存的所有键和值的字符串元素长度都小于64字节
- 哈希对象保存的元素数量小于512,
集合对象 集合对象可以是intset或者hashtable
如果集合里的值都是整数就用intset
文章图片
什么时候使用intset 同时满足才行
- 集合对象保存的所有元素都是整数
- 集合对象保存的元素数量小于512,
1、ziplist,每个集合元素使用两个紧挨着的压缩列表节点保存,第一节点保存成员,第二个元素保存分值
2、skiplist使用的是一个字典和跳跃表,跳跃表按分值从小到大保存所有集合元素,每个跳跃表节点保存一个集合元素,dict字典为有序集合创建了一个从成员到分值的映射,字典键保存元素成员,字典值保存元素分值。
/*
* 有序集合
*/
typedef struct zset {// 字典,键为成员,值为分值
// 用于支持 O(1) 复杂度的按成员取分值操作
dict *dict;
// 跳跃表,按分值排序成员
// 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
// 以及范围操作
zskiplist *zsl;
} zset;
文章图片
什么时候用ziplist 同时满足
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素成员的长度都小于64个字节
1、可以对任意类型的键执行,DEL,TYPE ,OBJECT等
2、对象的专用命令,set(字符串),hset(哈希),rpush(列表),sadd(集合),zadd(有序集合)。
那么问题就来了,在执行一个类型特定命令之前,redis必须要先检查类型,然后再决定是否执行
多态命令的实现 redis还会根据值对象的编码没选择正确的命令去执行。
现在,考虑这样一个情况,如果我们对一个键执行LLEN命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN命令实现:如果列表对象的编码为ziplist,那么说明列表对象的实现为压缩列表,程序将使
用ziplistLen函数来返回列表的长度;
如果列表对象的编码为linkedlist,那么说明列表对象的实现为双端链表,程序将使用listLength函数来返回双端链表的长度;
内存回收 总所周知C不没有垃圾回收器的,redis用一个引用技术技术实现的内存回收,程序跟踪对象的引用技术信息,再合适的时候自动释放内存
对象的引用计数信息会随着对象的使用状态而不断变化:
- 在创建一个新对象时,引用计数的值会被初始化为1;
- 当对象被一个新程序使用时,它的引用计数值会被增一;
- 当对象不再被一个程序使用时,它的引用计数值会被减一;
- 当对象的引用计数值变为0时,对象所占用的内存会被释放。
redis让多个键共享一个值对象
- 将数据库键的值指针指向一个现有的值对象
- 将被共享的值对象的引用计数加一
理论上所有对象都时可以共享的,当时再共享之前需要确认两个需要共享的元素是不是类型相同,时很好cup时间的,所有redis只包含了整数和字符串对象的共享。
对象的空转时长
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS;
/* lru time (relative to server.lruclock) */
object tdletime 键 可以打印lru
如果服务器开启了maxmemory选项,并且内存回收算法是 volatile-iru或者 allkeys-lru,那么服务器内存占用超过maxmemory时,空转时间长的会先被释放回收。
推荐阅读
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- Ⅴ爱阅读,亲子互动——打卡第178天
- 上班后阅读开始变成一件奢侈的事
- 历史教学书籍
- 绘本讲师训练营【24期】14/21阅读原创《小黑鱼》
- 21天|21天|M&M《见识》04
- 绘本讲师训练营7期9/21阅读原创《蜗牛屋|绘本讲师训练营7期9/21阅读原创《蜗牛屋 》
- 桂妃研读社|桂妃研读社|D124|如何有效阅读一本书 Day1
- 4.23世界阅读日,樊登读书狂欢放送,听书中成长
- 绘本讲师训练营【28期】15/21阅读原创《活了100万次的猫》