本文的分析都是基于 Redis 6.0 版本源码
redis 6.0 源码:https://github.com/redis/redis/tree/6.0
在 Redis 中,有五大数据类型,都统一封装成了一个数据类型:redisObject
。定义如下:
typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
unsigned lru:LRU_BITS; // 最近被访问的时间
int refcount; // 引用次数
void *ptr; // 指向具体底层数据的指针
} robj;
redisObject
结构示意图如下:
一、类型
typedef struct redisObject {
unsigned type:4; // 类型
} robj;
类型就是常见的五大对象类型。
新版本的常量命名是 OBJ 前缀,老版本是 REDIS 前缀(如 REDIS_STRING )
类型常量 | 对象名称 |
---|---|
OBJ_STRING | 字符串对象 |
OBJ_LIST | 列表对象 |
OBJ_SET | 集合对象 |
OBJ_ZSET | 有序集合对象 |
OBJ_HASH | 哈希对象 |
TYPE
命令可以输出指定 key 的值的对象类型。
redis > set str_key str_value
OK
redis > TYPE
string
对象名称 | TYPE 命令输出 |
---|---|
字符串对象 | string |
列表对象 | list |
集合对象 | set |
有序集合对象 | zset |
哈希对象 | hash |
二、编码
typedef struct redisObject {
unsigned encoding:4; // 编码
} robj;
五大类型中,每种类型可能存在着不同的编码。编码的存在主要是为了在节约内存和提高性能之间做平衡。
- 当数据量小的时候,会采用紧凑(性能偏低)的数据结构
- 当数据量达到一定阈值的时候,会从紧凑型的结构转成高效率的数据结构
由于篇幅有限,编码又比较复杂。所以编码就放到后面的文章写吧,嘿嘿嘿
三、最近被访问时间
typedef struct redisObject {
unsigned lru:LRU_BITS; // 最近被访问的时间
} robj;
当 Redis 的使用内存达到指定的阈值的时候,Redis 会对内存进行回收,回收有很多策略,其中就有 LRU 策略。
LRU 简单点来说就是,最近有修改或者访问的,就排在前面。等到要淘汰的时候,就淘汰排在后面的元素。淘汰的元素相对来说就是很久没有进行访问/修改了。
Redis 记录这个最近访问时间,就是为了 LRU 策略用的。
命令 OBJECT IDLETIME
可以查看 key 距离上次访问的时间。
由于篇幅有限,具体的淘汰策略就放到后面的文章吧,嘿嘿嘿。
四、引用次数
typedef struct redisObject {
int refcount; // 引用次数
} robj;
refcount
的作用主要是对象的引用计数和内存回收。refcount
会随着对象的使用状态而变化:
- 在创建一个新对象时,引用计数的值会被初始化为1;
- 当对象被一个新程序使用时,它的引用计数值会被增一;
- 当对象不再被一个程序使用时,它的引用计数值会被减一;
- 当对象的引用计数值变为0时,对象所占用的内存会被释放。
命令 OBJECT REFCOUNT
可以查看指定 key 的引用计数值。
共享对象
那么对象什么时候才会被新程序使用呢?
为了节省内存,Redis 会在初始化的时候,创建好 0 ~ 9999 的对象,后续 0 ~ 9999 的整数值都会用这些共享对象,不会重新创建对象。
server.h
#define OBJ_SHARED_INTEGERS 10000
object.c
// ...
if (value >= 0 && value < OBJ_SHARED_INTEGERS...)
// ...
五、指针
typedef struct redisObject {
void *ptr; // 指向具体底层数据的指针
} robj;
指针通常来说就是指向底层数据的。
不过有个例外,当值是 string
类型,并且编码是 int
时,保存的就是这个整数值,而不是指针。
关于底层数据的结构,后面会说。
网友评论