8.redisOb——redis数据结构

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有这样几个作用。

  1. 为所有类型的value提供一个统一的封装。
  2. 为数据淘汰保存必要的信息。
  3. 实现数据复用,和自动gc功能。

参考

https://wiki.jikexueyuan.com/project/redis/redis-object.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容