• Redis数据结构之robj


    本文及后续文章,Redis版本均是v3.2.8

    我们知道一个database内的这个映射关系是用一个dict来维护的。dict的key固定用一种数据结构来表达,这这数据结构就是动态字符串sds。而value则比较复杂,为了在同一个dict内能够存储不同类型的value,这就需要一个通用的数据结构。针对不同的使用场景,这个通用的数据结构可以使用不同的数据结构实现,这样可以优化在不同场景下的效率。这个通用的数据结构就是robj(redisObject),也是本文主要探讨的redis中的对象是怎么实现的。

    robj数据结构

    在server.h文件中,定义robj的相关代码

    /* A redis object, that is a type able to hold a string / list / set */

    /* The actual Redis Object */

    #define LRU_BITS 24

    #define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */

    #define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

    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: 对象的数据类型。占4个bit,可能的取值有5种:OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH,分别对应提供给我们长使用的5种数据结构(字符串对象(string)、列表对象(list)、哈希对象(hash)、集合对象(set)和有序集合对象(zset)),稍后再介绍5钟数据类型。

    • encoding: 对象的内部编码方式。占4个bit,可能的取值有10种,稍后再介绍10钟编码方式。

    • lru: lru time,占24个bit。

    • refcount: 引用计数。它允许robj对象在某些情况下被共享。

    • ptr: 数据指针,指向实现对象的底层数据结构。比如,一个代表string的robj,它的ptr可能指向一个sds结构。

    对象的类型type

    /* Object types */

    #define OBJ_STRING 0

    #define OBJ_LIST 1

    #define OBJ_SET 2

    #define OBJ_ZSET 3

    #define OBJ_HASH 4

    对象的编码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. */

    #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  /* 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 */

    #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */

    我们从以上的注释中知道,对于同一个type,还可能对应不同的encoding,这说明同样的一个数据类型,可能存在不同的内部编码方式。

    例如当type = OBJ_STRING时,表示这个robj存储的是一个string,那么encoding可以是下面3种选择:

    • OBJ_ENCODING_RAW: string采用原生的表示方式,即用sds来表示。

    • OBJ_ENCODING_INT: string采用数字的表示方式,实际上是一个long型。

    • OBJ_ENCODING_EMBSTR: string采用一种特殊的嵌入式的sds来表示。

    encoding:

    • OBJ_ENCODING_RAW: 最原生的表示方式。其实只有string类型才会用这个encoding值(表示成简单动态字符串sds)。

    • OBJ_ENCODING_INT: 表示成数字。实际用long表示。

    • OBJ_ENCODING_HT: 表示成dict。

    • OBJ_ENCODING_ZIPMAP: 是个旧的表示方式,已不再用。

    • OBJ_ENCODING_LINKEDLIST: 双端列表,已不再用

    • OBJ_ENCODING_ZIPLIST: 表示成ziplist。

    • OBJ_ENCODING_INTSET: 表示成intset。用于set数据结构。

    • OBJ_ENCODING_SKIPLIST: 表示成skiplist。用于sorted set数据结构。

    • OBJ_ENCODING_EMBSTR: 表示成一种特殊的嵌入式的sds。

    • OBJ_ENCODING_QUICKLIST: 表示成quicklist。用于list数据结构。

    访问时间lru

    lru属性(占24 bit)表示对象最后一次别访问的时间,根据lru判断对象是否应该被释放,本文暂不做分析。

    引用计数refcount

    由于C语言并不具备内存回收机制,所以redis通过refcount记录robj共享的次数。当refcount为0时即robj对象没有在被应用,表示该robj对象应该被释放,回收内存。

    我们看下object.c文件中代码

    void incrRefCount(robj *o) {

        o->refcount++;

    }

    void decrRefCount(robj *o) {

        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");

        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;

            default: serverPanic("Unknown object type"); break;

            }

            zfree(o);

        } else {

            o->refcount--;

        }

    }

    从代码可以看出,释放对象时,根据对象的类型type,释放对象保存的数据结构,再释放对象。

    例如释放set对象时:

    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");

        }

    }

    根据不同的编码实现,调用不同的底层释放函数。

    其他对象的操作函数都在object.c文件中,这里就不一一列举了。

    在上一篇文章《Redis数据结构之sds》中,我们简单地提到了sds与string的关系,当初我们简单的理解string对象就是sds。

    现在我们了解了robj的概念之后,我们在重新总结一下sds与string的关系:

    • string对象在Redis中是用一个robj(redisObject)来表示的。

    • string对象编码方式有3种:OBJ_ENCODING_RAW, OBJ_ENCODING_EMBSTR, OBJ_ENCODING_INT。其中前两种编码使用sds来存储,最后一种OBJ_ENCODING_INT编码直接把string存成了long型。

    • 在对string进行incr, decr等操作的时候,

      如果它内部是OBJ_ENCODING_INT编码,那么可以直接进行加减操作;如果它内部是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR编码,那么Redis会先试图把sds存储的字符串转成long型,如果能转成功,再进行加减操作。

    • 对一个内部表示成long型的string执行append, getrange, setbit等命令,针对的仍然是string的值(即十进制表示的字符串),而不是针对内部表示的long型进行操作。

    总结

    我们回顾下,robj的作用:

    • 为多种数据类型提供一种统一的表示方式。

    • 允许同一类型的数据采用不同的内部表示,从而在某些情况下尽量节省内存。

    • 支持对象共享和引用计数。当对象被共享的时候,只占用一份内存拷贝,进而节省内存。

    --EOF--

  • 相关阅读:
    JS-函数
    JS-数组
    JS-2
    课堂小技巧
    CSS利用filter/opacity实现背景透明
    [技巧心得] 背景半透明最佳实践
    Cadence Orcad 无法启动出现Capture.exe找不到cdn_sfl401as.dll问题
    正则表达式
    python小项目之头像右上角加数字
    Django开发之路 二(django的models表查询)
  • 原文地址:https://www.cnblogs.com/exceptioneye/p/6938010.html
Copyright © 2020-2023  润新知