redisOb简介
redis 是 key-value 存储系统,其中 key 类型一般为字符串,而 value 类型则为 redis 对象(redis object)。Redis 对象可以绑定各种类型的数据,譬如 string、list 和set。
typedef struct redisObject {
unsigned type:4; //数据类型
unsigned encoding:4;//数据编码
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
注:type a:n 表示变量a占4位
数据类型与数据编码
robj的type表明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. 哈希*/
每一种数据encoding 记录了对象所保存的值的编码,它的值可能是以下常量的其中一个(定义位于 redis.h)
#define OBJ_ENCODING_RAW 0 // 字符串
#define OBJ_ENCODING_INT 1 // 整数
#define OBJ_ENCODING_HT 2 // 哈希表
#define OBJ_ENCODING_ZIPMAP 3 // zipmap
#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 // ****
#define OBJ_ENCODING_QUICKLIST 9 // 这三个没见过啊,看看
#define OBJ_ENCODING_STREAM 10 // ****
下图展示了存的数据类型可以编码成什么格式
image.png
lru
redis提供了过期数据自动淘汰的策略,如何知道数据是否已经过期?按照什么样的策略淘汰数据?这俩问题的答案都和 lru 这个字段有关。redis给了lru这个字段24位,但千万别以为字段名叫lru就认为它只是LRU淘汰策略中才会使用的,其实LFU用的也是这个字段。 (估计是redis作者先写了lru策略,所以直接就叫lru了,后来再加lfu策略的时候直接复用这个字段了)
lru字段在不同淘汰策略时有不同的含义。
- 当使用LRU时,它就是一个24位的秒级unix时间戳,代表这个数据在第多少秒被更新过。
- 但使用LFU策略时,24位会被分为两部分,16位的分钟级时间戳和8位的特殊计数器
Redis命令访问缓存的数据时,均会调用函数lookupKey(),该函数在策略为LRU(非LFU)
时会更新对象的lru
值, 设置为LRU_CLOCK()值
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val);
} else {
val->lru = LRU_CLOCK();
}
}
return val;
} else {
return NULL;
}
}
refcount
引用计数,表示这个robj目前被多少个地方应用,refcount的出现为对象复用提供了基础。了解过垃圾回收的同学都知道有中回收策略就是采用计数器的方式,当refcount为0时,说明该对象已经没用了,就可以被回收掉了,redis的作者也实现了这种引用回收的策略。
- incrRefCount()函数用来增加引用计数
- decrRefCount()函数用来减少引用计数,如果归零就释放
void incrRefCount(robj *o) {
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
o->refcount++;
} else {
if (o->refcount == OBJ_SHARED_REFCOUNT) {
/* Nothing to do: this refcount is immutable. */
} else if (o->refcount == OBJ_STATIC_REFCOUNT) {
serverPanic("You tried to retain an object allocated in the stack");
}
}
}
void decrRefCount(robj *o) {
if (o->refcount == 1) {
switch(o->type) {
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
case OBJ_SET: freeSetObject(o); break;
case OBJ_ZSET: freeZsetObject(o); break;
case OBJ_HASH: freeHashObject(o); break;
case OBJ_MODULE: freeModuleObject(o); break;
case OBJ_STREAM: freeStreamObject(o); break;
default: serverPanic("Unknown object type"); break;
}
zfree(o);
} else {
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
}
}
ptr
这个就很简单了,前面几个字段是为当然robj提供meta信息,那这个字段就是数据具体所在地址。
总结
总结下,可以认为robj有这样几个作用。
- 为所有类型的value提供一个统一的封装。
- 为数据淘汰保存必要的信息。
- 实现数据复用,和自动gc功能。
参考
https://wiki.jikexueyuan.com/project/redis/redis-object.html