redis源码浅析--七-redisObject对象(上)(对象的类型与编码)

环境说明:redis源码版本 5.0.3;我在阅读源码过程做了注释,git地址:https://gitee.com/xiaoangg/redis_annotation

参考书籍:《redis的设计与实现》

文章推荐:
redis源码阅读-一--sds简单动态字符串
redis源码阅读--二-链表
redis源码阅读--三-redis散列表的实现
redis源码浅析--四-redis跳跃表的实现
redis源码浅析--五-整数集合的实现
redis源码浅析--六-压缩列表
redis源码浅析--七-redisObject对象(下)(内存回收、共享)
redis源码浅析--八-数据库的实现
redis源码浅析--九-RDB持久化
redis源码浅析--十-AOF(append only file)持久化
redis源码浅析--十一.事件(上)文件事件
redis源码浅析--十一.事件(下)时间事件
redis源码浅析--十二.单机数据库的实现-客户端
redis源码浅析--十三.单机数据库的实现-服务端 - 时间事件
redis源码浅析--十三.单机数据库的实现-服务端 - redis服务器的初始化
redis源码浅析--十四.多机数据库的实现(一)--新老版本复制功能的区别与实现原理
redis源码浅析--十四.多机数据库的实现(二)--复制的实现SLAVEOF、PSYNY
redis源码浅析--十五.哨兵sentinel的设计与实现
redis源码浅析--十六.cluster集群的设计与实现
redis源码浅析--十七.发布与订阅的实现
redis源码浅析--十八.事务的实现
redis源码浅析--十九.排序的实现
redis源码浅析--二十.BIT MAP的实现
redis源码浅析--二十一.慢查询日志的实现
redis源码浅析--二十二.监视器的实现

redis提供5种数据类型:字符串、列表、哈希、集合、有序集合;

实际上每种数据类型都有自己底层的内部编码实现;如set数据结构的底层编码方式有 压缩列表、跳表

这样做的好处就是 
1.可以针对不通的场景使用不同编码方式,不同的编码能够在各自场景下发挥优势;
2.当开发更优的编码方式时,可以无需改动外部数据结构和命令情况下 升级编码方式;

 

一 对象类型 与编码

redis中每个对象都是由redisObject来表示,redisObject定义位于server.h中;

//redisObjec结构体来表示string、hash、list、set、zset五种数据类型
typedef struct redisObject {
    //4位的type表示具体的数据类型()。Redis中共有5中数据类型(string、hash、list、set、zset)。
    //2^4 = 16足以表示这些类型
    unsigned type:4;
    //4位的encoding表示该类型的物理编码方式,同一种数据类型可能有不同的编码方式
    unsigned encoding:4; 
    //lru 属性保存了对象最后一次被命令访问的时间
    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;//refcount表示对象的引用计数
    void *ptr;//ptr指针指向真正的存储结构
} robj;

1. 类型type属性;

type占用4个bit,可以表示2^4个值;

type的值可以是: 0:字符串对象、1:列表对象、2:集合对象、3:有序集合对象、4:哈希对象

//redis的五种数据类型
/* 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. */

 

2.编码和底层实现 encoding属性;

同样encoding占用4个bit,可以表示2^4个值;

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. */
#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 /* No longer used: old list encoding. */ //双向链表
#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 */ //快速链表
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

//todo 每种type可用的编码方式 以及优缺点

类型可用编码
类型(type)编码(encoding)描述优缺点
OBJ_STRING

OBJ_ENCODING_RAW

//简单动态字符串

 

OBJ_ENCODING_INT

//long类型整数

 

OBJ_ENCODING_EMBSTR
(EMB : Embedded的缩写嵌入的 )

//编码的简单字符串; EMBSTR是专门保存简短字符串的一种优化编码方式

 
OBJ_LIST OBJ_ENCODING_ZIPLIST//压缩列表 
OBJ_ENCODING_LINKEDLIST//双向链表 
 OBJ_ENCODING_QUICKLIST 快速列表Redis中的列表对象在版本3.2之前,列表底层的编码是ziplist和linkedlist实现的,但是在版本3.2之后,重新引入了一个 quicklist 的数据结构,列表的底层都由quicklist实现。
OBJ_HASHOBJ_ENCODING_ZIPLIST//压缩列表 
OBJ_ENCODING_HT//字典 
OBJ_SETOBJ_ENCODING_INTSET//整数集合 
OBJ_ENCODING_HT//字典 
OBJ_ZSETOBJ_ENCODING_ZIPLIST//压缩列表 
OBJ_ENCODING_SKIPLIST//跳表 

tips:使用object encoding 命令可以查看一个对象的编码

 

二 字符串对象

字符串的编码方式可以是int、raw、或者embstr;

编码使用条件优缺点
int字符串对象保存的是整数 
raw值是一个字符串,并且 字符串的长度大于44字节创建/释放对象 需要两次内存操作;
embstr值是一个字符串,并且字符串的长度小于等于44字节

创建/释放对象 需要一次内存操作;

embstr编码的字符对象所有数据保存在一块连续的内存里,所以更高效;

 

 

 

 

以tryObjectEncoding 函数为入口,可以看到编码的整个过程:
 


//尝试对字符串进行编码,以节省空间
/* 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. */
    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. */
     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. */
    //检查字符串时候可以转化成整数;(字符串如果大于20位,64位存不下,所以一定不能转换)
    len = sdslen(s);
    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_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
            o->encoding = OBJ_ENCODING_INT;
            o->ptr = (void*) value;
            return o;
        }
    }

    /*
    如果字符串很小 并且 是使用 RAW编码的
    尝试使用 更高效的 EMBSTR编码方式
    */
    /* 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. */
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
        emb = createEmbeddedStringObject(s,sdslen(s)); //创建 EMB 编码的对象
        decrRefCount(o);
        return emb;
    }

    /*
    不能使用 long 和embstr编码方式
    
    */
    /* 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. */
    if (o->encoding == OBJ_ENCODING_RAW &&
        sdsavail(s) > len/10)
    {
        o->ptr = sdsRemoveFreeSpace(o->ptr);
    }

    /* Return the original object. */
    return o;
}

 

三 哈希对象

hash对象的编码的可以是ziplist或者是hashtable

  • 使用ziplist作为底层编码时,程序先将保存“键”的列表节点压如表尾,再将保存“值”的列表节点压如表尾;
  • 使用hashtable作为底层编码时,每个“键”都使用一个键值对都使用一个字典对象来保存;

3 .1编码转换

当满足以下两个条件时,使用ziplist编码:

  1. hash对象保存的所有键值对的键和值字符串长度都小于64字节
  2. 键值对数量小于512;

不能满足时则使用hashtable编码;

注:以上两个条件是可以修改的;
只支持ziplist转hashtale;


 /*
可以已hset命令为输入 阅读编码转换的工程
.......
*/

/* 检查是否需要编码转换 Check if the ziplist needs to be converted to a hash table */
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
   hashTypeConvert(o, OBJ_ENCODING_HT);
/*
.......
*/

 

四 集合对象

集合对象的编码方式可以是 intset或者是hashtable

  • intset作为底层编码,所有元素都被保存在整数集合里
  • hash作为底层实现时,元素都是字符串对象,存储hash键中,hash值设为NULL

4.1 编码转换

使用intset 的编码条件:

  1. 集合所有元素都是整数
  2. 集合元素个数少于512

不能满足时,使用hashtabl作为底层实现


//编码转换条件
/* Add the specified value into a set.
 *
 * If the value was already member of the set, nothing is done and 0 is
 * returned, otherwise the new element is added and 1 is returned. */
int setTypeAdd(robj *subject, sds value) {
    long long llval;
    if (subject->encoding == OBJ_ENCODING_HT) {
        dict *ht = subject->ptr;
        dictEntry *de = dictAddRaw(ht,value,NULL);
        if (de) {
            dictSetKey(ht,de,sdsdup(value));
            dictSetVal(ht,de,NULL);
            return 1;
        }
    } else if (subject->encoding == OBJ_ENCODING_INTSET) {
        if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {
            uint8_t success = 0;
            subject->ptr = intsetAdd(subject->ptr,llval,&success);
            if (success) {
                /* Convert to regular set when the intset contains
                 * too many entries. */
                if (intsetLen(subject->ptr) > server.set_max_intset_entries)
                    setTypeConvert(subject,OBJ_ENCODING_HT);
                return 1;
            }
        } else {
            /* Failed to get integer from object, convert to regular set. */
            setTypeConvert(subject,OBJ_ENCODING_HT);

            /* The set *was* an intset and this value is not integer
             * encodable, so dictAdd should always work. */
            serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK);
            return 1;
        }
    } else {
        serverPanic("Unknown set encoding");
    }
    return 0;
}

 

五 有序集合对象

有序集合编码可以是ziplist或者是skiplist

当ziplist作为有序集合实现时:

  • 每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素member,第二压缩列表节点保存分值;
  • 压缩列表元素按照分值从小到大进行排序

5.1编码转换

当同时满足以下两个条件时,使用ziplist编码

  1. 有序集合元素小于128
  2. 有序集合所有元素长度都小于64字节

tips:以上两个限制条件是可以根据配置来修改的

以zsetAdd函数作为入口阅读,可看到编码转换条件和转换过程
 

#define OBJ_ZSET_MAX_ZIPLIST_ENTRIES 128
#define OBJ_ZSET_MAX_ZIPLIST_VALUE 64

int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
   .....
        
            if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)//触发编码转换
                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
            if (sdslen(ele) > server.zset_max_ziplist_value) //触发编码转换
                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
            if (newscore) *newscore = score;
            *flags |= ZADD_ADDED;
            return 1;
     ......
    return 0; /* Never reached. */
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值