Redis根据基本数据结构构建了自己的一套对象系统。主要包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象
同时不同的对象都有属于自己的一些特定的redis指令集,而且每种对象也包括多种编码类型,和实现方式。
Redis对象结构
struct redisObject {
unsigned type:4; // 对象类型
unsigned encoding:4; // 编码方式
unsigned lru:LRU_BITS; // 最后一次的访问时间或者访问频率
int refcount; // 对象被引用的次数
void *ptr; // 指向底层数据结构实例
};
- type
分别表示字符串对象、列表对象、集合对象、有序集合对象和哈希对象。
/* The actual Redis Object */
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
- encoding
同一种对象可以根据元素的类型和大小,可以有选择不同的编码方式。
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
- lru
使用lru可以计算对象的空转时长 - refcount
对象的引用计数,在内存回收时会检查refcount - ptr
对象实例,保存对象的值
类型检查与命令多态
类型检查
有些命令针对不同的对象均适用,比如DEL、TTL等,但是有些命令仅适用于特定的对象类型,比如LLEN只对列表对象适用,所以在对对象执行某个命令前需要检查对象类型是否满足要求。满足要求则正常执行,否则返回报错。
以LLEN指令为例,指令执行流程如下
命令多态
命令多态包括基于类型的多态和基于编码的多态。
- 基于类型的多态:一个命令可以同时处理多钟不同类型的键。比如LLEN、DEL
- 基于编码的多态:一个命令可以同时用于处理多种不同的编码。比如LLEN对应的列表对象可以是ziplist编码或者linkedlist编码
内存回收
Redis使用refcount记录对象被引用次数,使用引用计数的原理来实现内存回收。
- 创建新对象是,refcount初始化为1
- 对象被新程序使用时,refcount增1
- 对象不再被程序使用时,refcount减1
- 对象的refcount变为0时,对象所占用的内存会被释放
对象共享
Redis为了尽可能的节省内存,还是引用了对象共享的机制。Redis会预创建一些对象,后续该对象被新程序引用时,不再创建新的对象,而是会将对象的refcount进行加1。
预创建的对象有常见OK、Error等错误指令还有[0-9999]的数字常量。
struct sharedObjectsStruct {
robj *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
*queued, *null[4], *nullarray[4], *emptymap[4], *emptyset[4],
*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *noscripterr, *loadingerr,
*slowevalerr, *slowscripterr, *slowmoduleerr, *bgsaveerr,
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
*rpop, *lpop, *lpush, *rpoplpush, *lmove, *blmove, *zpopmin, *zpopmax,
*emptyscan, *multi, *exec, *left, *right, *hset, *srem, *xgroup, *xclaim,
*script, *replconf, *eval, *persist, *set, *pexpireat, *pexpire,
*time, *pxat, *absttl, *retrycount, *force, *justid, *entriesread,
*lastid, *ping, *setid, *keepttl, *load, *createconsumer,
*getack, *special_asterick, *special_equals, *default_username, *redacted,
*ssubscribebulk,*sunsubscribebulk, *smessagebulk,
*select[PROTO_SHARED_SELECT_CMDS],
*integers[OBJ_SHARED_INTEGERS],
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
*bulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "$<value>\r\n" */
*maphdr[OBJ_SHARED_BULKHDR_LEN], /* "%<value>\r\n" */
*sethdr[OBJ_SHARED_BULKHDR_LEN]; /* "~<value>\r\n" */
sds minstring, maxstring;
};
注意Redis只对整数值的字符串对象共享。因为服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象作为键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象所需的复杂度就会越高,消耗的CPU时间也会越多。
- 如果共享对象是保存的字符串对象,那么验证操作的复杂度为O(1)
- 如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度是O(N)
- 如果共享对象是包含了多个值的对象,比如列表对象、哈希对象、集合对象时,验证操作的复杂度是O(N2)
对象空转时长
redis对象结构中还有lru字段,用于表示该对象最后一个被访问的时间。通过当前时间和lru记录时间可以计算出对象的空转时长。
当服务器占用的内存数超过了maxmemory的设置时,空转时长较高的键将会被优先释放,进行内存回收。