Redis根据基本数据结构构建了自己的一套对象系统。主要包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象
同时不同的对象都有属于自己的一些特定的redis指令集,而且每种对象也包括多种编码类型,和实现方式。
struct redisObject {
unsigned type:4; // 对象类型
unsigned encoding:4; // 编码方式
unsigned lru:LRU_BITS; // 最后一次的访问时间或者访问频率
int refcount; // 对象被引用的次数
void *ptr; // 指向底层数据结构实例
};
/* 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. */
/* 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 */
有些命令针对不同的对象均适用,比如DEL、TTL等,但是有些命令仅适用于特定的对象类型,比如LLEN只对列表对象适用,所以在对对象执行某个命令前需要检查对象类型是否满足要求。满足要求则正常执行,否则返回报错。
以LLEN指令为例,指令执行流程如下
命令多态包括基于类型的多态和基于编码的多态。
Redis使用refcount记录对象被引用次数,使用引用计数的原理来实现内存回收。
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], /* "*\r\n" */
*bulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "$\r\n" */
*maphdr[OBJ_SHARED_BULKHDR_LEN], /* "%\r\n" */
*sethdr[OBJ_SHARED_BULKHDR_LEN]; /* "~\r\n" */
sds minstring, maxstring;
};
注意Redis只对整数值的字符串对象共享。因为服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象作为键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象所需的复杂度就会越高,消耗的CPU时间也会越多。
redis对象结构中还有lru字段,用于表示该对象最后一个被访问的时间。通过当前时间和lru记录时间可以计算出对象的空转时长。
当服务器占用的内存数超过了maxmemory的设置时,空转时长较高的键将会被优先释放,进行内存回收。