对象
Redis基于后面的那些数据结构,创立了一个对象零碎用来实现键值对数据库。
那么就造成了五大根底对象
- 字符串对象 String
- 列表对象 List
- 哈希对象 Hash
- 汇合对象 Set
- 有序汇合对象 ZSet
因为引入了对象,所有Redis实现了基于援用计数技术的内存回收机制,当程序不在应用某个对象的时候,对象所占用的内存就会被主动开释,另外还实现了对象共享机制,在某些条件下多个数据库能够共享一个对象来节约内存。
类型与编码
Redis应用对象示意数据库中的键值,那么咱们创立一个键值对,咱们至多会创立两个对象
/* * Redis 对象 */#define REDIS_LRU_BITS 24#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->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 msg stringrpush numbers 1 3 5type numbers list
字符串对象
字符串对象的编码能够是 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调配的内存是间断的
如果你要存一个浮点数
那么程序会先将浮点数转换为字符串,而后保留的是字符串的值
编码转换
int和embstr对象在某些条件下会被转换为raw
对应int,如果咱们向对象执行了一些命令,使得之歌对象保留的不再是整数,而是一个字符串,那么int会变为raw
比方向一个整数追加一个字符串
set number 10086append number "hello"object encoding numberraw
redis并没有定义对embstr对象的API,所有其实上embstr字符串是只读的,当咱们理论对embstr字符串操作时,就会主动变raw
列表对象
编码能够时ziplist或者linkedlist
编码转换
什么时候应用ziplist 同时满足
- 列表对象保留的所有字符串元素长度都小于64字节
- 列表对象保留的元素数量小于512,
不满足这两个就用linkedlist
哈希对象
编码能够是ziplist或者hashtable
1、如果是ziplist会先push入键再push入值 都是在表尾 他们总是挨在一起的
2、如果是hashtable,那么hash对象中的每个键值对都是一个字典键值对保留的,
字典中键和值都是一个字符串对象
什么时候应用ziplist 同时满足才行
- 哈希对象保留的所有键和值的字符串元素长度都小于64字节
- 哈希对象保留的元素数量小于512,
不满足这两个就用hashtable
汇合对象
汇合对象能够是intset或者hashtable
如果汇合里的值都是整数就用intset
什么时候应用intset 同时满足才行
- 汇合对象保留的所有元素都是整数
- 汇合对象保留的元素数量小于512,
有序汇合
ziplist或者skiplist
1、ziplist,每个汇合元素应用两个紧挨着的压缩列表节点保留,第一节点保留成员,第二个元素保留分值
2、skiplist应用的是一个字典和跳跃表,跳跃表按分值从小到大保留所有汇合元素,每个跳跃表节点保留一个汇合元素,dict字典为有序汇合创立了一个从成员到分值的映射,字典键保留元素成员,字典值保留元素分值。
/* * 有序汇合 */typedef struct zset { // 字典,键为成员,值为分值 // 用于反对 O(1) 复杂度的按成员取分值操作 dict *dict; // 跳跃表,按分值排序成员 // 用于反对均匀复杂度为 O(log N) 的按分值定位成员操作 // 以及范畴操作 zskiplist *zsl;} zset;
什么时候用ziplist 同时满足
- 有序汇合保留的元素数量小于128个
- 有序汇合保留的所有元素成员的长度都小于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让多个键共享一个值对象
- 将数据库键的值指针指向一个现有的值对象
- 将被共享的值对象的援用计数加一
留神
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时,空转工夫长的会先被开释回收。