Redis对象
对象的类型与编码
- 每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)
- 每个对象都是由一个redisObject结构表示
typedef struct redisObject {
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
// ...
} robj;
类型
- 字符串对象 REDIS_STRING
- 编码可以是int、raw或者embstr
- 如果一个字符串对象保存的是整数值,并且这个整数值可以用long表示,用字符串编码会使用int
- 如果字符串对象保存的是字符串, 长度大于32字节,那么字符串对象使用简单动态字符串保存字符串值,编码记为raw
- 如果字符串对象保存的是字符串,长度小于等于32字节,使用embstr编码
- embstr与raw区别
- embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次
- 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
- 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势。
- 编码之间的转换
- int编码、embstr编码在一定条件下会转换为raw编码,如 APPEND方法等
- 列表对象 REDIS_LIST
- 编码可以为ziplist或者linkedlist
- 使用ziplist需要满足如下条件:
- 列表对象保存的所有字符串元素的长度都小于64字节
- 列表对象保存的数量小于512个
- 如果不满足使用ziplist的条件,则会转换为linkedlist(双端链表)
- 哈希对象 REDIS_HASH
- 编码可以为ziplist或者hashtable
- 使用ziplist
- 保存同一键值对的两个节点总是紧挨着, 保存键的节点在前, 保存值的节点在后
- 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。
- HASHTABLE编码(字典作为底层实现)
- 字典的每个键都是一个字符串对象,对象中保存了键值对的键;
- 字典的每个值都是一个字符串对象,对象中保存了键值对的值。
- 编码转换
- 字典的每个值都是一个字符串对象,对象中保存了键值对的值。
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
- 哈希对象保存的键值对数量小于512个;
- 注意: 以上两个条件可以修改设置
- 不能满足这两个条件的哈希对象需要使用hashtable编码。
- 集合对象 REDIS_SET
- 编码可以为intset或者hashtable
- intset
- intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面
- hashtable
- hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL。
- 编码转换
- 使用 intset编码条件
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过512个
- 不能满足intset条件的都为hashtable编码
- 有序集合对象 REDIS_ZSET
- 有序集合的编码可以是ziplist或者skiplist配合字典
- 有序集合为什么使用跳跃表和字典来实现?
- 单独使用一种结构性能都会降低
- 使用字典: 查找优秀,但是范围操作都要进行排序
- 使用跳跃表: 范围操作有点保存下来,但是根据成员去查找分值的能力降低
编码和底层实现
- 使用 OBJECT ENCODING 查看数据库键的值对象的编码
类型 | 编码 | 对象 |
---|
REDIS_STRING | REDIS_ENCDING_INT | 使用整数值实现的字符串对象 |
REDIS_STRING | REDIS_ENCDING_EMBSTR | 使用embstr编码的简单动态字符串对象实现的字符串对象 |
REDIS_STRING | REDIS_ENCDING_RAW | 使用简单动态字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用双端链表实现的列表对象 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的哈希对象 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典实现的哈希对象 |
REDIS_SET | REDIS_ENCODING_INTLIST | 使用整数集合实现的集合对象 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典实现的集合对象 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的有序集合对象 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 使用跳跃表和字典实现的列表对象 |
类型检查与命令多态
- 操作键的命令分为两种
- 对任何键执行(DEL、EXPIRE、RENAME、TYPE、OBJECT等)
- 对特定类型的键执行(SET、GET、APPEND、STRLEN等)
- 类型检查
- 在执行特定类型命令时,根据 redisObject 的 type属性检查是否可以执行
- 多态命令
- redis会根据值对象的类型来判断键是否能执行外,还会根据对象的编码来选择正确的命令实现代码来执行
内存回收
- 引用计数技术实现内存回收机制
- 在创建一个新对象时,引用计数的值会被初始化为1;
- 当对象被一个新程序使用时,它的引用计数值会被增一;
- 当对象不再被一个程序使用时,它的引用计数值会被减一;
- 当对象的引用计数值变为0时,对象所占用的内存会被释放
对象共享
- 步骤
- 将数据库键的值指针指向一个现有的值对象;
- 将被共享的值对象的引用计数增一
- 具体做法
- Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。
- 为什么不共享字符串对象
- 创建共享对象设置为键的值对象时,需要先检查键想创建的目标对象是否完全相同,只有完全相同才会创建
- 如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为O(1);
- 如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度为O(N);
- 如果共享对象是包含了多个值(或者对象的)对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N 2)
- 所以, 只共享数值类型数据
对象的空转时长-lru
- 除了对象的type、encoding、ptr、refcount四个属性外, redisObject结构还包含lru属性,用于记录对象最后一次被命令程序访问的时间
- OBJECT IDLETIME 命令可以打印出给定键的空转时长
- lru作用
- 计算空转时长
- 用于服务器释放内存
- volatile-lru
- allkeys-lru
- 空转时长较高的会优先被服务器释放