Redis 为了更好的实现键值对数据库,创建了一个对象系统,以下为 Redis 对象系统的相关知识简介。
redisObject
Redis 使用对象来表示数据库中的键和值,每次在 Redis 数据库中创建一个键值对时,至少会创建两个对象。一个为键对象,一个为值对象。
Redis 对象的定义如下:
typedef struct redisObject {
unsigned type:4; /* 对象类型 */
unsigned encoding:4; /* 对象编码 */
unsigned lru:LRU_BITS; /* 对象空转时长 */
int refcount; /* 引用计数 */
void *ptr; /* 指向底层数据结构的指针 */
} robj;
类型
对象的属性 type 记录了对象是什么类型。这个属性的值的相关定义如下:
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4
前面说到,键和值都是一个对象。键总是一个字符串对象,则属性 type 为 OBJ_STRING ,值对象则可以是字符串对象,列表对象等,不同的类型的对象,实则就是属性 type 不同的 redisObject 。如列表键,指的是属性 type 为 OBJ_LIST 的值对象。
可以通过使用命令 TYPE 在 Redis 客户端中获取指定键的数据类型。
127.0.0.1:6379> SET msg "hello redis"
OK
127.0.0.1:6379> TYPE msg
string
编码
对象的属性 encoding ,其实指的是该对象底层所使用的数据结构类型,即指针 ptr 所指向的数据结构,相关的定义如下:
#define OBJ_ENCODING_RAW 0 /* 简单动态字符串 */
#define OBJ_ENCODING_INT 1 /* 整型 */
#define OBJ_ENCODING_HT 2 /* 字典 */
#define OBJ_ENCODING_ZIPMAP 3 /* 压缩字典(不再使用)*/
#define OBJ_ENCODING_LINKEDLIST 4 /* 双端链表(不再使用) */
#define OBJ_ENCODING_ZIPLIST 5 /* 压缩列表 */
#define OBJ_ENCODING_INTSET 6 /* 整数集合 */
#define OBJ_ENCODING_SKIPLIST 7 /* 跳表 */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded 编码的简单动态字符串 */
#define OBJ_ENCODING_QUICKLIST 9 /* 快速列表,基于压缩列表实现的列表,取代了之前的双端链表 */
不同的对象类型,可以使用的对象编码也有所不同,下面为类型与编码的对应关系:
类型
编码
对象
OBJ_STRING
OBJ_ENCODING_INT
使用整数值实现的字符串对象
OBJ_STRING
OBJ_ENCODING_EMBSTR
使用 embstr 编码的简单动态字符串实现的字符串对象
OBJ_STRING
OBJ_ENCODING_RAW
使用简单动态字符串实现的字符串对象
OBJ_LIST
OBJ_ENCODING_QUICKLIST
使用 quicklist 实现的列表对象
OBJ_HASH
OBJ_ENCODING_ZIPLIST
使用压缩列表实现的哈希对象
OBJ_HASH
OBJ_ENCODING_HT
使用字典实现的哈希对象
OBJ_SET
OBJ_ENCODING_INTSET
使用整数集合实现的集合对象
OBJ_SET
OBJ_ENCODING_HT
使用字典实现的集合对象
OBJ_ZSET
OBJ_ENCODING_ZIPLIST
使用压缩列表实现的有序集合对象
OBJ_ZSET
OBJ_ENCODING_SKIPLIST
使用跳跃表实现的有序集合对象
通过命令 OBJECT ENCODING 命令可以在 Redis 客户端中查看指定键的对象编码。
127.0.0.1:6379> LPUSH DATABASE:LIST sqlserver mysql pgsql
(integer) 3
127.0.0.1:6379> OBJECT ENCODING DATABASE:LIST
"quicklist"
空转时间
对象属性 lru 记录的是对象最后一次被访问的时间。通过该属性,可以利用命令 OBJECT IDLETIME 获取对象的空转时长(命令 OBJECT IDLETIME 不会更新 lru 属性)。
OBJECT IDLETIME 计算得出的空转时长是通过当前时间减去指定对象的 lru 属性得出的,其单位为秒。
127.0.0.1:6379> set msg "hello world"
OK
127.0.0.1:6379> OBJECT IDLETIME msg
(integer) 10
属性 lru 除了应用于命令 OBJECT IDLETIME 外,还有另一项作用。
当服务器开启了 maxmemory 选项,并且服务器用于回收内存的算法为 volatile-lru 或者 allkeys-lru 的话,那么当 Redis 实例的占用的内存超过 maxmemory 后,空转时间较长的对象将会优先被回收。
引用计数
redisObject 还有一个属性 refcount ,它所记录的是对象被引用数。Redis 构建的对象系统使用引用计数机制实现内存回收。引用计算为 0 的对象将会被回收。
引用计数由一个特殊值,为 OBJ_SHARED_REFCOUNT ,该值为 INT_MAX 。当一个对象为共享对象时,该对象的 refcount 属性将会被赋予 OBJ_SHARED_REFCOUNT 。
每当需要使用一个对象时,都需要创建一个对象,而如果已经存在了一个一样的对象的话,再创建一个对象就会显得多余,完全可以共享相同的对象。而共享对象就是在 Redis 可以被共享使用的对象,共享对象在 Redis 中被多处使用,但指针所指向的对象都是同一个。
不过在 Redis 中,只有整数值小于 10000 的字符串对象可以被共享,在 Redis 实例启动时,会创建值为 0 到 9999 的类型为 OBJ_STRING ,编码为 OBJ_ENCODING_INT 的字符串对象。该值只可以通过修改源码 server.h 中的 OBJ_SHARED_INTEGERS 常量修改。
通过共享对象需要可以有效减少对象的数量从而减轻内存的压力,但是判断一个对象是否完全相同是需要进行运算,对象构成越复杂,计算的时间复杂度越高,相应的耗费的 CPU 资源就越多。共享对象为整型,计算的时间复杂度为 O(1) ,而共享对象为字符串时,计算复杂度为 O(N) ,换句话说就是时间置换空间,所以尽管共享对象可以节省空间,但是相应的会消耗 CPU 资源,所以 Redis 只针对整型字符串对象进行共享。
编码转换
redisObject 同一种类型可以有多种编码,即多种数据结构的实现。而同一类型的对象是可以进行编码的转换的,即底层数据结构可以进行转换。
控制对象编码转换的配置在配置文件 redis.conf 中(如果使用 redis.conf 作为配置文件启动的话)。
OBJ_STRING 类型的对象编码转换
字符串对象有三种编码,以下为三种编码的使用场景以及编码转换发生条件。
- OBJ_ENCODING_INT : 当对象所保存的值为 64 位整数值时,使用 OBJ_ENCODING_INT 作为对象的编码。
- OBJ_ENCODING_EMBSTR : 当对象保存的是一个字符串对象时,且其字节长度小于常量 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 时,对象使用 OBJ_ENCODING_EMBSTR 编码,常量 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 在源码文件 object.c 。embstr 编码是专门用于保存短字符串的一种优化编码方式,通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含 redisObject 和简单动态字符串两个结构,而且因为数据保存在同一块内存中,可以更好的利用缓存。
- OBJ_ENCODING_RAW : 当对象保存的是一个字符串对象时,且其字节长度大于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 时,对象使用 OBJ_ENCODING_RAW 编码。虽然 OBJ_ENCODING_RAW 没有 OBJ_ENCODING_EMBSTR 的优点,但是 OBJ_ENCODING_EMBSTR 编码的对象的值是不可变的,所以当字符串发生变化时,如使用命令 APPEND ,编码为 OBJ_ENCODING_EMBSTR 字符串对象将会转换成 OBJ_ENCODING_RAW 编码。
OBJ_HASH 类型的对象编码转换
相关配置项如下:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
配置的含义为:
- 哈希对象保存的元素数量小于 512 个
- 哈希对象保存的所有字符串元素的长度都小于 64 字节
不能满足上述条件的哈希对象将使用 OBJ_ENCODING_HT 作为对象编码,即使用字典作为底层数据结构。否则使用 OBJ_ENCODING_ZIPLIST 作为对象编码,即压缩列表作为底层数据结构。
OBJ_SET 类型的对象编码转换
相关配置如下:
set-max-intset-entries 512
配置的含义为:
- 集合对象保存的元素数量不超过512个
不能满足上述条件,或是元素不全部为整数( intset 数据结构的天然限制)的集合对象,将使用 OBJ_ENCODING_HT 作为对象编码,即使用字典作为底层数据结构。否则使用 OBJ_ENCODING_INTSET 作为对象编码,即 intset 作为底层数据结构。
OBJ_ZSET 类型的对象编码转换
相关配置如下:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
配置含义为:
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素成员的长度都小于64字节
不能满足上述条件的有序集合对象,将使用 OBJ_ENCODING_SKIPLIST 作为对象编码,即跳跃表作为底层数据结构。否则使用 OBJ_ENCODING_ZIPLIST 作为对象编码,即压缩列表作为底层数据结构。
OBJ_LIST 类型的对象编码转换
因为目前 OBJ_LIST 类型的对象只会使用一种编码 OBJ_ENCODING_QUICKLIST ,所以不会发生编码的转换。