一 序:

   前面整理了edis基本的数据结构分别进行了简单的介绍,包括字符串、链表、哈希表、整数集合、压缩列表、压缩字典等,但是redis并不是直接使用这些数据结构来实现key-value对数据库的,而是基于这些数据结构为每一个对象创建一个对象robject。通过这五种不同类型的对象, Redis 可以在执行命令之前, 根据对象的类型来判断一个对象是否可以执行给定的命令。 使用对象的另一个好处是, 我们可以针对不同的使用场景, 为对象设置多种不同的数据结构实现, 从而优化对象在不同场景下的使用效率。书上第8章比较长,接下来几篇分别整理,本篇整理object。

二 对象类型与编码

       Redis 使用对象来表示数据库中的键和值, 每次当我们在 Redis 的数据库中新创建一个键值对时, 我们至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象)。

     Redis 中的每个对象都由一个 redisObject 结构表示,源码在server.h:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

type:4bit,对象的类型
encoding:4bit,对象的编码方式
lru:24bit,记录访问时间
refcount:引用计数
ptr:指向具体的数据

   结合上面的属性,refcount基于引用计数技术的内存回收机制: 当程序不再使用某个对象的时候, 这个对象所占用的内存就会被自动释放; 另外, Redis 还通过引用计数技术实现了对象共享机制, 这一机制可以在适当的条件下, 通过让多个数据库键共享同一个对象来节约内存。lru:访问时间记录信息, 该信息可以用于计算数据库键的空转时长, 在服务器启用了 maxmemory 功能的情况下, 空转时长较大的那些键可能会优先被服务器删除。怎么实现的还没看。刨去这两个属性,跟保存数据有关的三个属性分别是 type 属性、 encoding 属性和 ptr 属性,下面分别来看。

2.1 type

对象的 type 属性记录了对象的类型, 这个属性的值可以是下面列出的常量的其中一个。

REDIS_STRING

字符串对象

REDIS_LIST

列表对象

REDIS_HASH

哈希对象

REDIS_SET

集合对象

REDIS_ZSET

有序集合对象

对应的源码在server.h:

/* Object types */
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

    对于 Redis 数据库保存的键值对来说, 键总是一个字符串对象, 而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种, 因此当我们称一个键为“字符串键”时,是指这个键对应的value是字符串类型的。

2.2 编码

   对象的 ptr 指针指向对象的底层实现数据结构, 而这些数据结构由对象的 encoding 属性决定。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. */
// encoding 的10种类型
#define OBJ_ENCODING_RAW 0     /* Raw representation */     //原始表示方式,字符串对象是简单动态字符串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */         //long类型的整数
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */      //字典
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */          //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */  //双端链表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */         //压缩列表
#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 */   //embstr编码的简单动态字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */   //由压缩列表组成的双向列表-->快速列表

每种类型的对象都至少使用了两种不同的编码,下面列出了每种对象可使用的编码方式。

类型 type

编码 encode

描述

OBJ_STRING

OBJ_ENCODING_INT

使用整数实现的字符串对象

OBJ_STRING

OBJ_ENCODING_EMBSTR

使用embstr编码实现的字符串对象

OBJ_STRING

OBJ_ENCODING_RAW

使用sds实现的字符串对象

OBJ_LIST

OBJ_ENCODING_QUICKLIST

使用quicklist实现的列表对象

OBJ_HASH

OBJ_ENCODING_ZIPLIST

使用压缩表实现的hash对象

OBJ_HASH

OBJ_ENCODING_HT

使用字典实现的hash对象

OBJ_SET

OBJ_ENCODING_INSET

使用整数集合实现的集合对象

OBJ_SET

OBJ_ENCODING_HT

使用字典实现的集合对象

OBJ_ZSET

OBJ_ENCODING_ZIPLIST

使用压缩列表实现的有序集合对象

OBJ_ZSET

OBJ_ENCODING_SKIPLIST

使用跳跃表实现的有序集合对象

  注意之前的list 使用链表在3.2代码已经没有了,只有quicklist, 通过 encoding 属性来设定对象所使用的编码, 而不是为特定类型的对象关联一种固定的编码, 极大地提升了 Redis 的灵活性和效率, 因为 Redis 可以根据不同的使用场景来为一个对象设置不同的编码, 从而优化对象在某一场景下的效率。

三 源码

看下object.c的源码:

/*
 * 为对象的引用计数增一
 */
void incrRefCount(robj *o) {
    o->refcount++;
}

/*
 * 为对象的引用计数减一
 *
 * 当对象的引用计数降为 0 时,释放对象。
 */
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通过refcount记录robj共享的次数。当refcount为0时即robj对象没有在被应用,表示该robj对象应该被释放,回收内存。释放对象时,根据对象的类型type,释放对象保存的数据结构,再释放对象。看下具体的释放对象,根据encoding去对应的不同的类型:

void freeStringObject(robj *o) {
    if (o->encoding == OBJ_ENCODING_RAW) {
        sdsfree(o->ptr);
    }
}

void freeListObject(robj *o) {
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
        quicklistRelease(o->ptr);
    } else {
        serverPanic("Unknown list encoding type");
    }
}

void freeSetObject(robj *o) {
    switch (o->encoding) {
    case OBJ_ENCODING_HT:
        dictRelease((dict*) o->ptr);
        break;
    case OBJ_ENCODING_INTSET:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown set encoding type");
    }
}

void freeZsetObject(robj *o) {
    zset *zs;
    switch (o->encoding) {
    case OBJ_ENCODING_SKIPLIST:
        zs = o->ptr;
        dictRelease(zs->dict);
        zslFree(zs->zsl);
        zfree(zs);
        break;
    case OBJ_ENCODING_ZIPLIST:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown sorted set encoding");
    }
}

void freeHashObject(robj *o) {
    switch (o->encoding) {
    case OBJ_ENCODING_HT:
        dictRelease((dict*) o->ptr);
        break;
    case OBJ_ENCODING_ZIPLIST:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown hash encoding type");
        break;
    }
}

LRU这块需要单独整理。

剩下的广域对象的创建,复制等结合具体的 类型来整理。