Redis对象介绍
我们知道Redis的主要数据结构有简单动态字符串、双端链表、字典、压缩列表、整数集合、跳跃表等。Redis并没有直接使用这些数据结构,而是基于这些数据结构创建了一个对象系统,这些对象系统包含字符串对象、列表对象、哈希对象、集合对象、有序集合对象。对应关系如下图。
此外,Redis的对象系统还实现了基于引用计数计数的内存回收机制、多个数据库之间的内存对象共享机制、记录数据库键的空转时长
每次当我们在Redis中新增一个键值对时,Redis会创建两个对象,一个键值对的键对象,另一个是键值对的值对象。
实现
Redis中每个对象由一个redisObject结构表示
typedef struct redisObject{
//类型
unsigned type;
//编码
unsigned encoding;
//指向底层实现数据结构的指针
void *ptr;
//引用计数
int refcount;
//空转时长
unsigned lru;
//...
}
type
redis中键值对,键肯定是一个字符串对象,值对象有以下五中,也就是我们常说的redis五种数据类型。当我们执行type命令时,redis返回的就是值对象结构中的type属性
常量 | 对象名称 |
---|---|
REDIS_STRING | 字符串对象 |
REDIS_LIST | 列表对象 |
REDIS_HASH | 哈希对象 |
REDIS_SET | 集合对象 |
REDIS_ZSET | 有序集合对象 |
encoding
encoding属性记录对象所使用的编码,也就是这个对象使用的是什么数据结构作为这个对象的底层实现。可以使用OBJECT ENCODING 命令查看键值对的编码。
通过encoding属性来设定对象所使用的编码,极大地提升了redis的灵活性和效率,因为redis可以根据不同的场景来为一个对象设置不同的编码。例如:因为压缩列表比双端链表更节约内存,并且在元素数量较少时,在内存中以连续内存块存储比分散链表形式存储读取更快而且还节省前后指针空间。
以下为OBJECT ENCODING命令对不同编码的输出情况
底层数据结构 | 编码常量 | 输出 |
---|---|---|
整数 | REDIS_ENCODING_INT | “int” |
embstr编码的简单动态字符串 | REDIS_ENCODING_EMBSTR | “embstr” |
简单动态字符串 | REDIS_ENCODING_RAW | “raw” |
跳跃表和字典 | REDIS_ENCODING_SKIPLIST | “skiplist” |
字典 | REDIS_ENCODING_HT | “hashtable” |
双端链表 | REDIS_ENCODING_INT | “linkedlist” |
压缩列表 | REDIS_ENCODING_ZIPLIST | “ziplist” |
整数集合 | REDIS_ENCODING_INTSET | “intset” |
refcount
- 对象共享,例如键A创建了一个整数值为100的字符串对象,新增键B时,直接让键B的值指针指向A的值对象,A的refcount引用计数+1。不过redis服务器初始化时会根据redis.h/REDIS_SHARED_INTEGERS创建值为0~9999的字符串对象。
- 内存回收,新建一个对象时,refcount值默认为1,当被一个新程序使用时,refcount加1,不在被一个程序使用时,refcount减1,当对象的refcount变为0时,对象所占用的内存会被释放。
lru
lru属性记录了对象最后一次被命令访问的时间。OBJECT IDLETIME 命令可以输出给定键值对的空转时长,空转时长=当前时间-键lru时间。当然执行OBJECT IDLETIME时不会更新lru属性。
lru最大用途是当服务器内存超过配置的maxmemory选项时,如果内存回收算法是volatile-lru和always-lru,空转时长较高的那部分键会被优先删除。
字符串对象
字符串对象的编码可以是int、raw、embstr
- 如果字符串对象保存的是整数值并且这个值可以用long类型来表示,那么字符串对象编码为int
- 如果字符串对象保存的是一个字符值,并且这个字符值长度大于39字节,那么字符串对象编码为raw
- 如果字符串对象保存的是一个字符值,并且这个字符值长度小于等于39字节,那么字符串对象编码为embstr
注意:如果值为浮点数,redis会转为字符串值进行存储,使用的时候先取出字符串在进行类型转换
raw编码和embstr编码区别
- embstr通过执行一次内存分配函数来分配一块连续的内存空间给redisObject和sdshdr两个结构,而raw通过两次来分配的
- 释放embstr编码的字符串对象只需要调用一次内存释放函数,而raw编码的需要调用两次
- 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里,所以这种编码的字符串对象比raw编码的能更好地利用缓存带来的优势
列表对象
列表对象的编码可以是ziplist或者linkedlist,同时满足以下两个条件,列表对象使用ziplist编码,否则使用linkedlist
- 列表内元素长度都小于64字节
- 列表对象保存的元素数量小于512个
这两个条件的配置也可以通过配置文件修改,list-max-ziplist-value和list-max-ziplist-entries选项
哈希对象
哈希对象的编码可以是ziplist或者hashtable,同时满足以下两个条件,哈希对象使用ziplist编码,否则使用hashtable
- 哈希对象保存的所有键值对的键和值长度都小于64字节
- 哈希对象保存的键值对数量小于512个
这两个条件的配置也可以通过配置文件修改,hash-max-ziplist-value和hash-max-ziplist-entries选项
集合对象
集合对象的编码可以是intset和hashtable,同时满足以下两个条件,集合对象使用intset编码,否则使用hashtable
- 集合对象保存的所有元素都是整数值
- 集合对象保存的对象不超过512个
这两个条件的配置也可以通过配置文件修改,set-max-ziplist-entries选项
有序集合对象
有序集合对象的编码可以是ziplist和skiplist,同时满足以下两个条件,有序集合对象使用ziplist编码,否则使用skiplist
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素成员的长度小于64字节
这两个条件的配置也可以通过配置文件修改,zset-max-ziplist-value和zset-max-ziplist-entries选项