redis是一个key-value存储系统,其中key类型一般为字符串,而value类型则为redis对象。Redis对象可以绑定各种类型的数据,如string、hash、list、set和zset。redisObejct在redis.h中定义。
1. 定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef struct redisObject { unsigned type:4 ; unsigned encoding:4 ; unsigned lru:REDIS_LRU_BITS; int refcount; void *ptr; } robj;
1.1 type redisObject共有以下5种类型,type占4bit(理论支持2^4=16种)
1 2 3 4 5 6 7 #define REDIS_STRING 0 #define REDIS_LIST 1 #define REDIS_SET 2 #define REDIS_ZSET 3 #define REDIS_HASH 4
1.2 encoding redisObject共有以下9种编码类型,占用4bit,如“12345”既可以用字符串编码,也可能被存储为一个整数。
1 2 3 4 5 6 7 8 9 10 11 #define REDIS_ENCODING_RAW 0 #define REDIS_ENCODING_INT 1 #define REDIS_ENCODING_HT 2 #define REDIS_ENCODING_ZIPMAP 3 #define REDIS_ENCODING_LINKEDLIST 4 #define REDIS_ENCODING_ZIPLIST 5 #define REDIS_ENCODING_INTSET 6 #define REDIS_ENCODING_SKIPLIST 7 #define REDIS_ENCODING_EMBSTR 8
编码类型
底层实现
OBJ_ENCODING_RAW
简单动态字符串sds
OBJ_ENCODING_INT
long类型的整数
OBJ_ENCODING_HT
字典dict
OBJ_ENCODING_LINKEDLIST
双端队列sdlist
OBJ_ENCODING_ZIPLIST
压缩列表ziplist
OBJ_ENCODING_INTSET
整数集合intset
OBJ_ENCODING_SKIPLIST
跳跃表skiplist和字典dict
OBJ_ENCODING_EMBSTR
EMBSTR编码的简单动态字符串sds
OBJ_ENCODING_QUICKLIST
由双端链表和压缩列表构成的快速列表
redis的每一种对象类型可以对应不同的编码方式,极大地提高了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
1.3 lru lru用来表示该对象最后一次被访问的时间,其占用24个bit位。Redis对数据集占用内存大小有实时计算,当超出限额时,会淘汰超时的数据。
1.4 refcount 引用计数,一个 Redis 对象可能被多个指针引用。C语言不具备自动内存回收机制,所以Redis对每一个对象设定了引用计数refcount字段,程序通过该字段的信息,在适当的时候自动释放内存进行内存回收。此功能与C++的智能指针相似。当需要增加或者减少引用的时候,必须调用相应的函数,相应实现在object.c中。
当创建一个对象时,其引用计数初始化为1;
当这个对象被一个新程序使用时,其引用计数加1;
当这个对象不再被一个程序使用时,其引用计数减1;
当引用计数为0时,释放该对象,回收内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void incrRefCount (robj *o) { o->refcount++; } void decrRefCount (robj *o) { if (o->refcount <= 0 ) redisPanic("decrRefCount against refcount <= 0" ); if (o->refcount == 1 ) { switch (o->type) { case REDIS_STRING: freeStringObject(o); break ; case REDIS_LIST: freeListObject(o); break ; case REDIS_SET: freeSetObject(o); break ; case REDIS_ZSET: freeZsetObject(o); break ; case REDIS_HASH: freeHashObject(o); break ; default : redisPanic("Unknown object type" ); break ; } zfree(o); } else { o->refcount--; } }
因为redis是单进程单线程工作的,所以增加/减少引用的操作不必要保证原子性。
1.5 ptr 空类型指针,意味着可以指向任何类型的数据,用来存储真正的数据。
2. 主要方法 操作redisObject的主要方法在object.c中,主要包括以下函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 robj *createObject (int type, void *ptr) ; robj *createStringObject (const char *ptr, size_t len) ; robj *createRawStringObject (const char *ptr, size_t len) ; robj *createEmbeddedStringObject (const char *ptr, size_t len) ; robj *createStringObjectFromLongLong (long long value) ; robj *createStringObjectFromLongDouble (long double value, int humanfriendly) ; robj *createQuicklistObject (void ) ; robj *createZiplistObject (void ) ; robj *createSetObject (void ) ; robj *createIntsetObject (void ) ; robj *createHashObject (void ) ; robj *createZsetObject (void ) ; robj *createZsetZiplistObject (void ) ;
以下我们以字符串为例,展开介绍。
2.1 创建字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39 robj *createStringObject (char *ptr, size_t len) { if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) return createEmbeddedStringObject(ptr,len); else return createRawStringObject(ptr,len); } robj *createRawStringObject (char *ptr, size_t len) { return createObject(REDIS_STRING,sdsnewlen(ptr,len)); } robj *createEmbeddedStringObject (char *ptr, size_t len) { robj *o = zmalloc(sizeof (robj)+sizeof (struct sdshdr)+len+1 ); struct sdshdr *sh = (void *)(o +1); o->type = REDIS_STRING; o->encoding = REDIS_ENCODING_EMBSTR; o->ptr = sh+1 ; o->refcount = 1 ; o->lru = LRU_CLOCK(); sh->len = len; sh->free = 0 ; if (ptr) { memcpy (sh->buf,ptr,len); sh->buf[len] = '\0' ; } else { memset (sh->buf,0 ,len+1 ); } return o; } robj *createObject (int type,。 void *ptr) { robj *o = zmalloc(sizeof (*o)); o->type = type; o->encoding = REDIS_ENCODING_RAW; o->ptr = ptr; o->refcount = 1 ; o->lru = LRU_CLOCK(); return o; }
RawString和EmbeddedString区别
RawString中robj和sdshdr是单独分配内存的,而EmbeddedString是一起分配内存的。EmbeddedString内存的申请和释放都只需要一次,连续的内存空间可以更好地利用缓存优势,缺点是Redis未提供修改EmbeddedString的方法。
2.2 字符串回收 1 2 3 4 5 6 void freeStringObject (robj *o) { if (o->encoding == REDIS_ENCODING_RAW) { sdsfree(o->ptr); } }
总结
Redis使用RedisObject来统一表示各种数据类型,包括String/Set/Zset/List/Hash
redis通过引用计数法表示对象生存状态,单进程单线程的工作原理使得引用计数的变化不需要保证原子性。