2023-03-09  阅读(0)
原文作者:men_wen 原文地址:https://blog.csdn.net/men_wen/article/details/70145752

Redis 对象系统

1. 介绍

redis中基于双端链表、简单动态字符串(sds)、字典、跳跃表、整数集合、压缩列表、快速列表等等数据结构实现了一个对象系统,并且实现了5种不同的对象,每种对象都使用了至少一种前面的数据结构,优化对象在不同场合下的使用效率。

2. 对象的系统的实现

redis 3.2版本。所有注释在github中:对象系统的注释

2.1 对象的结构

对象结构robj功能:

  • 为5种不同的对象类型提供同一的表示形式。
  • 为不同的对象适用于不同的场景,支持同一种对象类型采用多种的数据结构方式。
  • 支持引用计数,实现对象共享机制。
  • 记录对象的访问时间,便于删除对象。

对象结构定义在redis 3.2版本的server.h

    #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 {
        //对象的数据类型,占4bits,共5种类型
        unsigned type:4;        
        //对象的编码类型,占4bits,共10种类型
        unsigned encoding:4;
    
        //least recently used
        //实用LRU算法计算相对server.lruclock的LRU时间
        unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    
        //引用计数
        int refcount;
    
        //指向底层数据实现的指针
        void *ptr;
    } robj;
    
    //type的占5种类型:
    /* Object types */
    #define OBJ_STRING 0    //字符串对象
    #define OBJ_LIST 1      //列表对象
    #define OBJ_SET 2       //集合对象
    #define OBJ_ZSET 3      //有序集合对象
    #define OBJ_HASH 4      //哈希对象
    
    /* 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 */   //由压缩列表组成的双向列表-->快速列表

2.2 字符串对象的底层实现类型

编码—encoding 对象—ptr
OBJ_ENCODING_RAW 简单动态字符串实现的字符串对象
OBJ_ENCODING_INT 整数值实现的字符串对象
OBJ_ENCODING_EMBSTR embstr编码的简单动态字符串实现的字符串对象

2.3 列表对象的底层实现类型

编码—encoding 对象—ptr
OBJ_ENCODING_QUICKLIST 快速列表实现的列表对象
OBJ_ENCODING_ZIPLIST 压缩列表实现的列表对象

2.4 集合对象的底层实现类型

编码—encoding 对象—ptr
OBJ_ENCODING_HT 字典实现的集合对象
OBJ_ENCODING_INTSET 整数集合实现的集合对象

2.5 哈希对象的底层实现类型

编码—encoding 对象—ptr
OBJ_ENCODING_ZIPLIST 压缩列表实现的哈希对象
OBJ_ENCODING_HT 字典实现的哈希对象

2.6 有序集合对象的底层实现类型

编码—encoding 对象—ptr
OBJ_ENCODING_SKIPLIST  跳跃表和字典 实现的有序集合对象
OBJ_ENCODING_ZIPLIST 压缩列表实现的有序集合对象

3. 对象系统的重要操作

3.1创建一个字符串对象

  • 编码为OBJ_ENCODING_RAW
    robj *createObject(int type, void *ptr) {   //创建一个对象
        robj *o = zmalloc(sizeof(*o));          //分配空间
        o->type = type;                         //设置对象类型
        o->encoding = OBJ_ENCODING_RAW;         //设置编码方式为OBJ_ENCODING_RAW
        o->ptr = ptr;                           //设置
        o->refcount = 1;                        //引用计数为1
    
        /* Set the LRU to the current lruclock (minutes resolution). */
        o->lru = LRU_CLOCK();                   //计算设置当前LRU时间
        return o;
    }
  • 编码为OBJ_ENCODING_EMBSTR
    /* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
     * an object where the sds string is actually an unmodifiable string
     * allocated in the same chunk as the object itself. */
    //创建一个embstr编码的字符串对象
    robj *createEmbeddedStringObject(const char *ptr, size_t len) {
        robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);   //分配空间
        struct sdshdr8 *sh = (void*)(o+1);  //o+1刚好就是struct sdshdr8的地址
    
        o->type = OBJ_STRING;               //类型为字符串对象
        o->encoding = OBJ_ENCODING_EMBSTR;  //设置编码类型OBJ_ENCODING_EMBSTR
        o->ptr = sh+1;                      //指向分配的sds对象,分配的len+1的空间首地址
        o->refcount = 1;                    //设置引用计数
        o->lru = LRU_CLOCK();               //计算设置当前LRU时间
    
        sh->len = len;                      //设置字符串长度
        sh->alloc = len;                    //设置最大容量
        sh->flags = SDS_TYPE_8;             //设置sds的类型
        if (ptr) {                          //如果传了字符串参数
            memcpy(sh->buf,ptr,len);        //将传进来的ptr保存到对象中
            sh->buf[len] = '\0';            //结束符标志
        } else {
            memset(sh->buf,0,len+1);        //否则将对象的空间初始化为0
        }
        return o;
    }
  • 两种字符串对象编码方式的区别
    /* Create a string object with EMBSTR encoding if it is smaller than
     * REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
     * used.
     *
     * The current limit of 39 is chosen so that the biggest string object
     * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
    
    //sdshdr8的大小为3个字节,加上1个结束符共4个字节
    //redisObject的大小为16个字节
    //redis使用jemalloc内存分配器,且jemalloc会分配8,16,32,64等字节的内存
    //一个embstr固定的大小为16+3+1 = 20个字节,因此一个最大的embstr字符串为64-20 = 44字节
    #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
    
    // 创建字符串对象,根据长度使用不同的编码类型
    // createRawStringObject和createEmbeddedStringObject的区别是:
    // createRawStringObject是当字符串长度大于44字节时,robj结构和sdshdr结构在内存上是分开的
    // createEmbeddedStringObject是当字符串长度小于等于44字节时,robj结构和sdshdr结构在内存上是连续的
    robj *createStringObject(const char *ptr, size_t len) {
        if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
            return createEmbeddedStringObject(ptr,len);
        else
            return createRawStringObject(ptr,len);
    }

3.2 字符串对象编码的优化

    /* Try to encode a string object in order to save space */
    //尝试优化字符串对象的编码方式以节约空间
    robj *tryObjectEncoding(robj *o) {
        long value;
        sds s = o->ptr;
        size_t len;
    
        /* Make sure this is a string object, the only type we encode
         * in this function. Other types use encoded memory efficient
         * representations but are handled by the commands implementing
         * the type. */
        serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
    
        /* We try some specialized encoding only for objects that are
         * RAW or EMBSTR encoded, in other words objects that are still
         * in represented by an actually array of chars. */
        //如果字符串对象的编码类型为RAW或EMBSTR时,才对其重新编码
        if (!sdsEncodedObject(o)) return o;
    
        /* It's not safe to encode shared objects: shared objects can be shared
         * everywhere in the "object space" of Redis and may end in places where
         * they are not handled. We handle them only as values in the keyspace. */
        //如果refcount大于1,则说明对象的ptr指向的值是共享的,不对共享对象进行编码
         if (o->refcount > 1) return o;
    
        /* Check if we can represent this string as a long integer.
         * Note that we are sure that a string larger than 20 chars is not
         * representable as a 32 nor 64 bit integer. */
        len = sdslen(s);            //获得字符串s的长度
    
        //如果len小于等于20,表示符合long long可以表示的范围,且可以转换为long类型的字符串进行编码
        if (len <= 20 && string2l(s,len,&value)) {
            /* This object is encodable as a long. Try to use a shared object.
             * Note that we avoid using shared integers when maxmemory is used
             * because every object needs to have a private LRU field for the LRU
             * algorithm to work well. */
            if ((server.maxmemory == 0 ||
                 (server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
                  server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
                value >= 0 &&
                value < OBJ_SHARED_INTEGERS)    //如果value处于共享整数的范围内
            {
                decrRefCount(o);                //原对象的引用计数减1,释放对象
                incrRefCount(shared.integers[value]); //增加共享对象的引用计数
                return shared.integers[value];      //返回一个编码为整数的字符串对象
            } else {        //如果不处于共享整数的范围
                if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);   //释放编码为OBJ_ENCODING_RAW的对象
                o->encoding = OBJ_ENCODING_INT;     //转换为OBJ_ENCODING_INT编码
                o->ptr = (void*) value;             //指针ptr指向value对象
                return o;
            }
        }
    
        /* If the string is small and is still RAW encoded,
         * try the EMBSTR encoding which is more efficient.
         * In this representation the object and the SDS string are allocated
         * in the same chunk of memory to save space and cache misses. */
        //如果len小于44,44是最大的编码为EMBSTR类型的字符串对象长度
        if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
            robj *emb;
    
            if (o->encoding == OBJ_ENCODING_EMBSTR) return o;   //将RAW对象转换为OBJ_ENCODING_EMBSTR编码类型
            emb = createEmbeddedStringObject(s,sdslen(s)); //创建一个编码类型为OBJ_ENCODING_EMBSTR的字符串对象
            decrRefCount(o);    //释放之前的对象
            return emb;
        }
    
        /* We can't encode the object...
         *
         * Do the last try, and at least optimize the SDS string inside
         * the string object to require little space, in case there
         * is more than 10% of free space at the end of the SDS string.
         *
         * We do that only for relatively large strings as this branch
         * is only entered if the length of the string is greater than
         * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
        //无法进行编码,但是如果s的未使用的空间大于使用空间的10分之1
        if (o->encoding == OBJ_ENCODING_RAW &&
            sdsavail(s) > len/10)
        {
            o->ptr = sdsRemoveFreeSpace(o->ptr);    //释放所有的未使用空间
        }
    
        /* Return the original object. */
        return o;
    }

3.3 引用计数管理对象

    //引用计数加1
    void incrRefCount(robj *o) {
        o->refcount++;
    }
    
    //引用计数减1
    void decrRefCount(robj *o) {
        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
    
        //当引用对象等于1时,在操作引用计数减1,直接释放对象的ptr和对象空间
        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--;  //否则减1
        }
    }

3.4 对象的复制,创建的对象非共享

    //返回 复制的o对象的副本的地址,且创建的对象非共享
    robj *dupStringObject(robj *o) {
        robj *d;
    
        serverAssert(o->type == OBJ_STRING);    //一定是OBJ_STRING类型
    
        switch(o->encoding) {                   //根据不同的编码类型
        case OBJ_ENCODING_RAW:
            return createRawStringObject(o->ptr,sdslen(o->ptr));        //创建的对象非共享
        case OBJ_ENCODING_EMBSTR:
            return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));   //创建的对象非共享
        case OBJ_ENCODING_INT:                  //整数编码类型
            d = createObject(OBJ_STRING, NULL); //即使是共享整数范围内的整数,创建的对象也是非共享的
            d->encoding = OBJ_ENCODING_INT;
            d->ptr = o->ptr;
            return d;
        default:
            serverPanic("Wrong encoding.");
            break;
        }
    }

3.5 对象的解码操作

将保存的整数值解码成字符串对象返回回来。

    /* Get a decoded version of an encoded object (returned as a new object).
     * If the object is already raw-encoded just increment the ref count. */
    //将对象是整型的解码为字符串并返回,如果是字符串编码则直接返回输入对象,只需增加引用计数
    robj *getDecodedObject(robj *o) {
        robj *dec;
    
        if (sdsEncodedObject(o)) {  //如果是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR类型的对象
            incrRefCount(o);        //增加引用计数,返回一个共享的对象
            return o;
        }
        if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) { //如果是整数对象
            char buf[32];
    
            ll2string(buf,32,(long)o->ptr); //将整数转换为字符串
            dec = createStringObject(buf,strlen(buf));  //创建一个字符串对象
            return dec;
        } else {
            serverPanic("Unknown encoding type");
        }
    }

3.6 其他操作

所有注释在github中:对象系统的注释


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文