Redis存储原理与数据模型(下)

2.2、string-最基本的数据类型

  • 是二进制安全的,可以包含任何数据。比如jpg图片或者序列化的对象等.
  • 最大能存储512M,动态数组,会+1M进行扩容
  • 安全:不同于C语言以’\0’作为字符串的结束标志,而是以长度来决定字符串的结束位置。
  • 提供了三种不同的编码方式(int, embstr, raw)
    在这里插入图片描述

结合上面的dictEntry结构体,可以发现,k1和V1都是字符串,但是作者都没直接使用C语言的字符数组或者指针来进行存储,而是void*指针

再执行OBJECT ENCODING K1得到的结果是embstr,说明底层使用的是动态字符串sds。

而value并不是直接指向sds,而是指向了redisObject对象,再通过redisObject的 ptr指向sds

回顾redisObject结构体:

struct redisObject {
    unsigned type:4;         // 当前值的数据类型,比如string、list等
    unsigned encoding:4;    // 当前值对象底层存储编码方式,比如int、embstr等

    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;              // 指向底层实现数据结构,比如哈希表、双向链表等
};

/* 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  /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#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 listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

作者通过typr以及encoding字段来区分底层的数据结构,进而实现不同的功能。

在这里插入图片描述

/*  具体实现,路径:redis\src\object.c  */ 
char *strEncoding(int encoding) {
    switch(encoding) {
    case OBJ_ENCODING_RAW: return "raw";
    case OBJ_ENCODING_INT: return "int";
    case OBJ_ENCODING_HT: return "hashtable";
    case OBJ_ENCODING_QUICKLIST: return "quicklist";
    case OBJ_ENCODING_LISTPACK: return "listpack";
    case OBJ_ENCODING_LISTPACK_EX: return "listpackex";
    case OBJ_ENCODING_INTSET: return "intset";
    case OBJ_ENCODING_SKIPLIST: return "skiplist";
    case OBJ_ENCODING_EMBSTR: return "embstr";
    case OBJ_ENCODING_STREAM: return "stream";
    default: return "unknown";
    }
}

SDS动态字符串:

//在redis\src\sds.h中定义了sds的结构体
typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

在结构体中可以看到:

len:表示已使用的长度

alloc:表示分配的长度(不包括头部和结尾的’\0’),也可以用来计算free未使用的空间,实现动态扩容,缩容

flags:用来标识类型,是sdshdr8还是sdshdr16等

buf 字段: 用于存储字符串的实际内容。它是一个变长数组,其大小由 alloc 字段决定。

这种结构体,作者采用柔性数组的特性来定义

  • 结构体中,数组必须放在最后
  • 数组必须是不固定的
  • 结构体除了柔性数组,还必需包含其他成员
  • sizeof返回结构体大小时,不包含柔性数组的大小
  • 可以在单个内存块中高效地管理结构体和关联的数据,从而简化内存管理和提高程序性能。
//这种结构体如何malloc, 以sdshdr8为例
char* str = "hello world";
size_t total_size = sizeof(struct sdshdr8) + strlen(str) + 1; // 计算总大小,包括头部、字符串内容和结尾的空字符 '\0'
struct sdshdr8* sds = (struct sdshdr8*)malloc(total_size);
if (sds == NULL) {
    perror("malloc failed");
    return NULL;
}

// 初始化结构体成员
sds->len = (uint8_t)strlen(str);
sds->alloc = (uint8_t)(sds->len + 1); // 包括结尾的空字符 '\0'
sds->flags = 0; // 根据需要设置 flags
    
// 复制字符串到柔性数组
strcpy(sds->buf, str);

物理编码方式:

  • int:保存long型64位,8个字节的有符号整数,19位数字;只保存整数,不能保存浮点数;
  • embstr: 保存小于等于44字节的字符串
  • raw:保存大于44字节的字符串
那redis是如何实现编码转化的?

首先Redis启动时会预先建立10000个redisObject变量作为共享对象,当set字符串的键值在10000以内时,就直接使用共享对象,无序创建新的redisObject变量,节省内存空间。

set k1 1234567890    
set k2 1234567890         # 此时k1和k2指向同一个对象,k2不占空间 <=====> C++的引用,int& k2 = k1
/* path: server.h */
#define OBJ_SHARED_INTEGERS 10000

/* PATH: object.c */
/* Try to encode a string object in order to save space */
robj *tryObjectEncodingEx(robj *o, int try_trim) {
    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. */
    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {      // 长度 <= 20, 可以转换为long类型,则尝试共享对象或者转为int编码
        /* 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);                        
            return shared.integers[value];
        } else {
            if (o->encoding == OBJ_ENCODING_RAW) {      // 编码为RAW时,转换INT
                sdsfree(o->ptr);
                o->encoding = OBJ_ENCODING_INT;
                o->ptr = (void*) value;
                return o;
            } else if (o->encoding == OBJ_ENCODING_EMBSTR) {    // 编码为EMBSTR时,转换到RAW
                decrRefCount(o);
                return createStringObjectFromLongLongForValue(value);
            }
        }
    }

    /* 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));
        decrRefCount(o);
        return emb;
    }

    /* We can't encode the object...
     * Do the last try, and at least optimize the SDS string inside */
    if (try_trim)
        trimStringObjectIfNeeded(o, 0);

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

字符长度未超过44字节,为什么会变成RAW编码?

在这里插入图片描述

对于embstr,由于是只读的,当在对embstr进行修改时,会先转换成RAW编码再进行修改,因此只要是修改操作,都会转换成RAW编码,无论是否会超过44字节。

小结:

  • C数组和柔性数组的区别:


C数组SDS
字符串长度处理需要从头开始遍历,直到遇到’\0’,时间复杂度O(n)直接通过len字段获取,时间复杂度O(1)
内存重新分配分配的内存空间超过后,会导致下标越界,溢出,要重新分配内存,拷贝数据alloc空间预分配,扩容缩容时,只需要拷贝len长度的数据
二进制安全不是二进制安全的,会将’\0’之后的字符也当做字符串的末尾是二进制安全的,可以直接存储任意数据,包括’\0’
  • redis会根据不同的键值类型,自动选择不同的编码方式
  • 当对embstr进行修改时,会先转换成RAW编码再进行修改,与是否会超过44字节无关。

2.3、hash

redis6.0版本及之前,hash == 压缩列表 + 哈希表<br
redis7.0版本之后,hash == listpack(紧凑列表) + 哈希表

hash在Redis6.0版本中的实现

在redis6.0版本中,执行以下命令:

config get hash*        

# redis6.0版本
1) "hash-max-ziplist-entries" 
2) "512"
3) "hash-max-ziplist-value
4) "64"

配置参数:

hash-max-ziplist-entries:使用压缩列表保存时,集合中最大元素个数

hash-max-ziplist-value:使用压缩列表保存时,集合中的单个元素最大长度

如果以上两个条件,任意一个不满足,那么就会使用哈希表来保存hash键值对。

/* t_hash.c */
void hsetCommand(client *c) {
    int i, created = 0;
    robj *o;

    if ((c->argc % 2) == 1) {
        addReplyErrorArity(c);
        return;
    }

    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
    hashTypeTryConversion(c->db,o,c->argv,2,c->argc-1);             // 尝试将压缩列表转换为哈希表,如果满足条件则转换。

    for (i = 2; i < c->argc; i += 2)
        created += !hashTypeSet(c->db, o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);

    /* HMSET (deprecated) and HSET return value is different. */
    char *cmdname = c->argv[0]->ptr;
    if (cmdname[1] == 's' || cmdname[1] == 'S') {
        /* HSET */
        addReplyLongLong(c, created);
    } else {
        /* HMSET */
        addReply(c, shared.ok);
    }
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
    server.dirty += (c->argc - 2)/2;
}

void hashTypeTryConversion(redisDb *db, robj *o, robj **argv, int start, int end) {
    int i;
    size_t sum = 0;

    if (o->encoding != OBJ_ENCODING_ZIPLIST)
        return;

    for (i = start; i <= end; i++) {
        if (!sdsEncodedObject(argv[i]))
            continue;
        size_t len = sdslen(argv[i]->ptr);
        if (len > server.hash_max_ziplist_value) {
            hashTypeConvert(o, OBJ_ENCODING_HT);
            return;
        }
        sum += len;
    }
    if (!ziplistSafeToAdd(o->ptr, sum))
        hashTypeConvert(o, OBJ_ENCODING_HT);
}

可以看到底层在处理时,与string类似,根据元素个数来选择最优的编码方式。

注意:

  • ziplist升级到hashtable可以,但是反过来不行。
ziplist到底是什么?

ziplist是redis为了节约内存而设计的一种数据结构,总体思想:以时间换空间 它将多个连续的entry存储在一块连续的内存空间中,以此来减少内存碎片的开销,并且通过指针偏移量来定位元素的位置。

它一个双向链表,但不存储指向前一个链表节点prev和指向下一个链表节点next,而是存储上一个节点长度和当前节点长度

/* ----------------------------------------------------------------------------
 * path: ziplist.c
 * 
 * ZIPLIST OVERALL LAYOUT
 * ======================
 *
 * The general layout of the ziplist is as follows:
 *
 * <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>  
   ......    
 */
 typedef struct zlentry {
    unsigned int prevrawlensize; /* 上一个链表节点占用长度*/
    unsigned int prevrawlen;     /* 存储上一个链表节点的长度数值所需要的字节数 */
    unsigned int lensize;        /* 存储当前链表节点长度数值所需要的字节数 */
    unsigned int len;            /* 当前链表节点占用的长度 */
    unsigned int headersize;     /* 当前链表节点的头部大小 */
    unsigned char encoding;      /* 编码方式 */
    unsigned char *p;            /* 压缩链表以字符串的形式保存,指向当前节点的起始位置 */
} zlentry;

在这里插入图片描述

在这里插入图片描述

ziplist为entry开辟一段连续的内存,如果知道当前节点的起始地址,那么可以得知:

上个节点的起始地址 = 当前节点的起始地址 - 上一个节点长度

这个方法在 查询速度上比传统的双端链表要快,而且指针也要额外占用内存空间

hash在Redis7.0版本中的实现

config get hash*

# redis7.0版本
1) "hash-max-listpack-value"
2) "64"
3) "hash-max-listpack-entries"
4) "512"
5) "hash-max-ziplist-value"
6) "64"
7) "hash-max-ziplist-entries"
8) "512"

可以看到新增了两个配置参数,不过redis7.0版本中,并没有使用这两个参数,只是为了版本兼容而已.

配置参数:

hash-max-listpack-entries: 使用listpack保存时,集合中最大元素个数

hash-max-listpack-value: 使用listpack保存时,集合中的单个元素最大长度

/* object.c */
robj *createHashObject(void) {
    unsigned char *zl = lpNew(0);
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_LISTPACK;                            // 默认使用listpack编码方式创建hash对象
    return o;
}

/* listpack.c */
#define LP_HDR_SIZE 6       /* 32 bit total len + 16 bit number of elements. */
unsigned char *lpNew(size_t capacity) {
    unsigned char *lp = lp_malloc(capacity > LP_HDR_SIZE+1 ? capacity : LP_HDR_SIZE+1);
    if (lp == NULL) return NULL;
    lpSetTotalBytes(lp,LP_HDR_SIZE+1);
    lpSetNumElements(lp,0);
    lp[LP_HDR_SIZE] = LP_EOF;
    return lp;
}

创建默认是6个字节,其中4个字节是记录listpack的总长度,2个字节记录元素个数,最后一个字节是LP_EOF标识结束,值为255.

已经有了ziplist,为什么还要引入listpack?

***ziplist的缺点:***当新增或更新元素可能会出现连续更新现象.

在这里插入图片描述

所以listpack就是为了解决这个连续更新问题,才诞生,通过每个节点记录自己的长度且放在节点的末尾来实现的,不再记录上一个节点的长度

listpack的结构:

在这里插入图片描述

|total bytes|为整个listpack的空间大小,占用4个字节|
|num-elements|为listpack中元素的个数,占用2个字节|
|entry-1~N|每个具体元素,占用4个字节记录长度,后面跟着具体的值|
|listpack-eof|为listpack的结束标识,值为255,占1个字节|

在这里插入图片描述

2.4、list

执行config get list*查看下相关配置:

config get list*

1) "list-compress-depth"
2) "0"
3) "list-max-listpack-size"
4) "-2"
5) "list-max-ziplist-size"
6) "-2"

可以看到类似于hash,redis7.0版本为了兼容,也保留了ziplist

  • list-compress-depth: 表示一个quicklist两端不被压缩的节点个数
    • 0 :表示不进行压缩,默认值
    • 1 :表示quicklist两端各有1个节点不压缩,中间的节点压缩
    • 2 :表示quicklist两端各有2个节点不压缩,中间的节点压缩
      … 以此类推
  • list-max-ziplist-size: 当取正值时,表示按照数据项个数来限定ziplist的大小,当取负值时,表示按照占用字节大小来限定ziplist的大小。
    • -2 :每个quicklist节点上的ziplist大小不能超过8KB,默认值
    • -1 :每个quicklist节点上的ziplist大小不能超过4KB
    • 5 :每个quicklist节点上的ziplist大小不能超过5个元素
  • list-max-listpack-size: 和list-max-ziplist-size一样
/* t_list.c */
/* LPUSH <key> <element> [<element> ...] */
void lpushCommand(client *c) {
    pushGenericCommand(c,LIST_HEAD,0);
}

/* RPUSH <key> <element> [<element> ...] */
void rpushCommand(client *c) {
    pushGenericCommand(c,LIST_TAIL,0);
}

/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
 * 'xx': push if key exists. */
void pushGenericCommand(client *c, int where, int xx) {
    int j;

    robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
    if (checkType(c,lobj,OBJ_LIST)) return;
    if (!lobj) {
        if (xx) {
            addReply(c, shared.czero);
            return;
        }

        lobj = createListListpackObject();
        dbAdd(c->db,c->argv[1],lobj);
    }

    listTypeTryConversionAppend(lobj,c->argv,2,c->argc-1,NULL,NULL);
    for (j = 2; j < c->argc; j++) {
        listTypePush(lobj,c->argv[j],where);
        server.dirty++;
    }

    addReplyLongLong(c, listTypeLength(lobj));

    char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}

通过前面hash对底层ziplist和listpack的了解,Redis7.0与6.0版本,list在实现上就把原来的ziplist换成listpack。

2.5、set

set == 整数集合 + 哈希表

整数集合是什么?

整数集合(intset)是 Redis 为了节约内存而设计的特殊数组,它可以保存所有元素的升序排列,且不包含任何重复元素。整数集合的底层实现为 int16_t 、int32_t 或者 int64_t。

void saddCommand(client *c) {
    robj *set;
    int j, added = 0;

    set = lookupKeyWrite(c->db,c->argv[1]);
    if (checkType(c,set,OBJ_SET)) return;

    if (set == NULL) {
        set = setTypeCreate(c->argv[2]->ptr, c->argc - 2);
        dbAdd(c->db,c->argv[1],set);
    } else {
        setTypeMaybeConvert(set, c->argc - 2);
    }

    for (j = 2; j < c->argc; j++) {
        if (setTypeAdd(set,c->argv[j]->ptr)) added++;
    }
    if (added) {
        signalModifiedKey(c,c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
    }
    server.dirty += added;
    addReplyLongLong(c,added);
}

/* returned, otherwise the new element is added and 1 is returned. */
int setTypeAdd(robj *subject, sds value) {
    return setTypeAddAux(subject, value, sdslen(value), 0, 1);
}

/* Add member. This function is optimized for the different encodings. The
 * value can be provided as an sds string (indicated by passing str_is_sds =
 * 1), as string and length (str_is_sds = 0) or as an integer in which case str
 * is set to NULL and llval is provided instead.
 *
 * Returns 1 if the value was added and 0 if it was already a member. */
int setTypeAddAux(robj *set, char *str, size_t len, int64_t llval, int str_is_sds) {
    char tmpbuf[LONG_STR_SIZE];
    if (!str) {
        if (set->encoding == OBJ_ENCODING_INTSET) {             // 整数集合
            uint8_t success = 0;
            set->ptr = intsetAdd(set->ptr, llval, &success);
            if (success) maybeConvertIntset(set);
            return success;
        }
        /* Convert int to string. */
        len = ll2string(tmpbuf, sizeof tmpbuf, llval);
        str = tmpbuf;
        str_is_sds = 0;
    }

    serverAssert(str);
    if (set->encoding == OBJ_ENCODING_HT) {                     // 哈希表
        /* Avoid duping the string if it is an sds string. */
        sds sdsval = str_is_sds ? (sds)str : sdsnewlen(str, len);
        dict *ht = set->ptr;
        void *position = dictFindPositionForInsert(ht, sdsval, NULL);
        if (position) {
            /* Key doesn't already exist in the set. Add it but dup the key. */
            if (sdsval == str) sdsval = sdsdup(sdsval);
            dictInsertAtPosition(ht, sdsval, position);
        } else if (sdsval != str) {
......
        }
    }
}

2.6、zset

Redis6.0: zset == 跳表 + 压缩列表

Redis7.0: zset == 跳表 + 紧凑列表

什么是跳表?

回顾下单链表,插入、删除快,但是查找效率为O(n);对其进行优化,以空间换时间,给链表增加索引,使得查找效率提升至O(logn)。

明显可以看出,添加索引之后,查找一个节点需要遍历的次数变少了。

跳表是可以实现二分查找的有序链表,总之就是跳表 = 链表 + 多级索引

在这里插入图片描述

为什么使用跳表?

  • 跳跃表的实现方式,使得它在查找、删除、添加等操作都可以在对数期望时间复杂度内完成。
  • 跳跃表在内存中的存储结构类似于多层链表,通过这种方式可以快速定位到元素的位置。
  • 跳跃表相比于平衡树等其他数据结构来说,它的实现更为简单,而且效率也相对较高。
  • 在Redis中,跳跃表主要用于有序集合的实现,它可以保证元素的唯一性并且能够按照一定的顺序进行排序。因此,跳跃表成为了Redis中最常用的数据结构之一。

时间复杂度与空间复杂度

时间复杂度: O(logn)

空间复杂度:O(n)

优点:

  • 以空间换时间,在数据量大的时候才能体现出来查询优势
  • 适合读多写少的场景

缺点:

  • 索引也需要占用内存空间。
  • 一旦新增或者删除元素,都需要把索引重新更新一遍

三、总结:

    1. redis从命令处理的角度来看,是单线程的。
    1. 从整体架构来看,是多线程的,使用了多个线程来处理不同类型的后台任务。
    1. Redis高效的数据结构:

      redis6.0版本以及之前:

      string == sds

      list == quciklist(压缩列表)

      hash == 压缩列表 + 哈希表

      set == 整数集合 + 哈希表

      zset == 跳表 + 压缩列表

      redis7.0版本之后:

      string == sds

      list == quciklist(listpack)

      hash == listpack(紧凑列表) + 哈希表

      set == 整数集合 + 哈希表

      zset == 跳表 + listpack
    1. C数组和柔性数组的区别:


C数组SDS
字符串长度处理需要从头开始遍历,直到遇到’\0’,时间复杂度O(n)直接通过len字段获取,时间复杂度O(1)
内存重新分配分配的内存空间超过后,会导致下标越界,溢出,要重新分配内存,拷贝数据alloc空间预分配,扩容缩容时,只需要拷贝len长度的数据
二进制安全不是二进制安全的,会将’\0’之后的字符也当做字符串的末尾是二进制安全的,可以直接存储任意数据,包括’\0’

四、问题:

4.1、处于渐进式 rehash 阶段时,是否会发生扩容缩容?

不会!

4.2、为什么redis中字符串选择64个字节作为分界线?

  • 首先内存分配器是按照大小为2^n来进行分配的,而且CPU最小访问单位是64字节,所以选择64字节作为分界线。
  • 其次string由于结构较为特殊,buf可用字节数为44字节,计算过程如下:
    在这里插入图片描述
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

64 - 16 - 3 - 1 = 44,多减去的1是因为C语言字符串结尾的’\0’。
0vice·GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值