各种数据结构 ( sds、dict、skiplist、intset、ziplist 等 ),作为 Redis对外提供的各种数据类型的底层组成部分;但是各种数据类型的键值对并不是直接使用这些数据结构,而是基于这些数据结构构建了一个对象系统(字符串对象、列表对象、哈希对象、集合对象、有序集合对象 )
每种对象都至少使用了一种数据结构。
对象特性
Redis 在执行命令之前,根据对象的类型就可以判断 命令是否可以执行
依托对象,可以针对不同的使用场景,为对象设置不同的数据结构实现,从而优化对象在不同场景下的使用效率
Redis 对象系统实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放
Redis 使用引用计数技术实现了对象共享机制,一定场景下,可以通过让多个数据库键共享同一个对象来节约内存
Redis 对象带有访问时间记录,可以用来计算数据库键的空转时间,在服务器启用 maxmemory 的情况下,空转时长较大的键可能被优先删除
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 // 哈希对象
Redis键值对,键总是字符串对象,而值是各种对象,比如常说的列表键,实际就是列表键对象,键为字符串对象,值是列表对象
encoding
#define OBJ_ENCODING_RAW 0 // 简单动态字符串
#define OBJ_ENCODING_INT 1 // long 类型整数
#define OBJ_ENCODING_HT 2 // 字典
#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 // embstr 编码的简单动态字符串
#define OBJ_ENCODING_QUICKLIST 9 // 由双端链表和压缩列表构成的快速列表
Redis 的每一种对象类型可以对应不同的编码方式,这就极大地提升了 Redis 的灵活性和效率。可以根据不同的使用场景,来选择合适的编码方式,五种对象类型对应的底层编码方式如下
+------------------------------------------------+
+ 对象类型 编码方式
+------------------------------------------------+
+ OBJ_STRING OBJ_ENCODING_RAW
+ OBJ_ENCODING_INT
+ OBJ_ENCODING_EMBSTR
+------------------------------------------------+
+ OBJ_LIST OBJ_ENCODING_LINKEDLIST
+ OBJ_ENCODING_ZIPLIST
+ OBJ_ENCODING_QUICKLIST
+------------------------------------------------+
+ OBJ_SET OBJ_ENCODING_INTSET
+ OBJ_ENCODING_HT
+------------------------------------------------+
+ OBJ_ZSET OBJ_ENCODING_ZIPLIST
+ OBJ_ENCODING_SKIPLIST
+------------------------------------------------+
+ OBJ_HASH OBJ_ENCODING_ZIPLIST
+ OBJ_ENCODING_HT
+------------------------------------------------+
lru 24bit
表示该对象最后一次被访问的时间,为了计算该对象的空转时长,便于后续根据空转时长来决定是否释放该键,回收内存。通过命令OBJECT IDLETIME查看对象的空转时间
如果服务器打开了 maxmemory 选项,在一定情况下 会 启动 lru 算法进行淘汰:
volatile-lru 根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止
allkeys-lru 根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止
refcount
引用计数。redis通过引用计数实现内存回收机制,程序可以通过引用计数在适当的时候释放内存并内存回收。在创建一个对象的时候,引用计数的值会初始化为 1。
当对象被一个新程序使用时,它的引用计数值会增加 1
当对象不再被一个程序使用时,它的引用计数值会减去 1
当对象的引用计数值为 0 的时候,对象占用的内存会被释放
对象共享
对象的引用计数还带有对象共享功能。Redis 默认会建立0 ~ 9999 ( 通过 server.h: line 92 的常量 OBJ_SHARED_INTEGERS 控制 ) 每个数的对象
也就是说 Redis 在初始化的时候已经把这10000个数字对象建立了,只要后续使用到的这个范围内的数字都会直接引用这些对象。
为什么共享对象只是使用了数字字符串呢?
因为服务器考虑将一个对象设置为键的值对象时,需要先检查给定共享对象跟想要创建的对象是否完全相同,只有在共享对象和目标对象完全相同的时候,程序才会使用 共享对象,所以共享对象保存的值越复杂,那么这个对比过程CPU消耗率就越高。
数字字符串的对比验证复杂度为 O(1),字符串对比验证复杂度为 O(n),列表对象或者哈希对象的验证复杂度为 O(n2)*。所以尽管共享对象更为节约内存,但是受CPU运算的限制,Redis只包含整数值字符串的共享对象