Redis深度历险 - embstr和raw的字符串

2021-04-17  本文已影响0人  突击手平头哥

Redis深度离线 - embstr和raw的字符串

在Redis中字符串存储有两种方式,embstr和raw两种形式,不超过44字节的情况下以embstr存储,超过44字节则以raw形式存储

image-002.png

\color{red}{注:Redis中底层保证字符串的存储必然以0结尾,虽然是44个字符但是obj显示确是45}


embstr vs raw

Redis字符串结构体

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[];
};

  Redis字符串的实现原理是在在字符串头部额外分配空间存储上述这样一个结构体,使用变长结构体存储长度、容量等信息;视长度而采取不通的结构体。

  字符串较短是SDS头部最少占用3个字节(sdshdr5废弃),不过在Redis中上述SDS字符串只是基础数据结构并不由Redis直接使用

Redis对象结构体

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    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;
} robj;

#define LRU_BITS 24

  所有Redis对象都有上述结构体,上述结构体长度为(4+4+24+4*8+8*8)/8即16字节;分配一个字符串最少头部就需要占用19个字节。

创建字符串对象

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

  在创建字符串结构时通过宏定义根据长度区分创建的方式

/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
 * string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution), or
     * alternatively the LFU counter. */
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    return o;
}

  创建SDS字符串然后将指针赋给RedisObject的ptr中。

/* 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. */
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->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }

    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

  创建字符串对象时直接创建了RedisObj+sdsHeader+string的连续空间,即RedisOjbect的空间和SDS字符串是连在一起的

结构说明

image-001.png

为什么是44个字节?

  因为Redis中内存是统一控制分配的,通常是是2、4、8、16、32、64等;64-19-1=44就是原因。

上一篇 下一篇

猜你喜欢

热点阅读