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结构体:

c 复制代码
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字段来区分底层的数据结构,进而实现不同的功能。

c 复制代码
/*  具体实现,路径: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动态字符串:

c 复制代码
//在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返回结构体大小时,不包含柔性数组的大小
  • 可以在单个内存块中高效地管理结构体和关联的数据,从而简化内存管理和提高程序性能。
c 复制代码
//这种结构体如何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变量,节省内存空间。

bash 复制代码
set k1 1234567890    
set k2 1234567890         # 此时k1和k2指向同一个对象,k2不占空间 <=====> C++的引用,int& k2 = k1
c 复制代码
/* 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版本中,执行以下命令:

bash 复制代码
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键值对。

c 复制代码
/* 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,而是存储上一个节点长度和当前节点长度

c 复制代码
/* ----------------------------------------------------------------------------
 * 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版本中的实现

bash 复制代码
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保存时,集合中的单个元素最大长度

c 复制代码
/* 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*查看下相关配置:

bash 复制代码
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一样
c 复制代码
/* 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。

c 复制代码
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字节,计算过程如下:
c 复制代码
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

相关推荐
xmjd msup4 分钟前
mysql的分区表
数据库·mysql
Lyyaoo.4 分钟前
【JAVA Spring面经】Spring 事务失效情况
java·数据库·spring
MeAT ITEM9 分钟前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dovens13 分钟前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.113 分钟前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库
Rick199322 分钟前
mysql 慢查询怎么快速定位
android·数据库·mysql
科技小花7 小时前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化
X56619 小时前
如何在 Laravel 中正确保存嵌套动态表单数据(主服务与子服务)
jvm·数据库·python
虹科网络安全10 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
2301_7717172110 小时前
解决mysql报错:1406, Data too long for column
android·数据库·mysql