在 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 时,保存的就是这个整数值,而不是指针。

关于底层数据的结构,后面会说。

本文的分析没有特殊说明都是基于 Redis 6.0 版本源码
redis 6.0 源码:https://github.com/redis/redis/tree/6.0