[redis]数据结构(1)

119 阅读6分钟

sds 字符串

char*表示,文件sds.h。定义

// __attribute__ ((__packed__)) 取消结构体字节对齐,按紧凑排列
struct __attribute__ ((__packed__)) sdshdr8 {  // len和alloc是uint8_t类型
    uint8_t len; /* used 已使用长度,不包括\0 */
    uint8_t alloc; /* excluding the header and null terminator 已分配长度*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits 低三位表示类型,高5位没有使用*/
    char buf[]; /*保存的字符串内容,以'\0'结束 */
};

list

list实现为一个双向链表

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;


typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);  // 函数实现
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    unsigned long len;
} list;

迭代器包含一个指针和一个方向

typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

set

无序集合

set正常使用hash table实现;但当集合中只包含整数元素,且元素不多时,redis使用intset作为set的底层实现

set使用dict实现,key是sds string,value没有使用

intset类型:

typedef struct intset {
    uint32_t encoding; // 编码方式,使用16位/32位/64位编码
    uint32_t length;   // 元素数量
    int8_t contents[]; // 数据
} intset;

intset类型是用固定长度表示每个key,encoding标识了编码方式(是使用64位呢还是32位还是16位表示),contents中为有序排列的set元素值,在intset中查找元素使用二分查找,效率更高

创建什么样类型的set判断:

robj *setTypeCreate(sds value) {
    // 可以用longlong表示,就创建intset,否则创建一般的dict
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
        return createIntsetObject();
    return createSetObject();
}

在使用intset时,有两种情况会转换类型到hash table:1.新添加元素不能转成int,把intset转成HT,2.intset的元素数目>server.set_max_intset_entries了

intset添加元素:

intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    uint8_t valenc = _intsetValueEncoding(value);  // 编码位数,最长64,最小16
    uint32_t pos;
    if (success) *success = 1;


    /* Upgrade encoding if necessary. If we need to upgrade, we know that
     * this value should be either appended (if > 0) or prepended (if < 0),
     * because it lies outside the range of existing values. */
    if (valenc > intrev32ifbe(is->encoding)) { // 编码长度不够用了,需要升级一下
        /* This always succeeds, so we don't need to curry *success. */
        // 升级encoding类型,新元素append到最后端或者最前端
        return intsetUpgradeAndAdd(is,value);
    } else {
        /* Abort if the value is already present in the set.
         * This call will populate "pos" with the right position to insert
         * the value when it cannot be found. */
        // 使用二分查找找到新插入的value应该在的位置pos,已经有这个value了,直接返回
        if (intsetSearch(is,value,&pos)) {
            if (success) *success = 0;
            return is;
        }


        is = intsetResize(is,intrev32ifbe(is->length)+1);
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1); // 使用memmove拷贝字节
    }


    _intsetSet(is,pos,value); // 插入新元素
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

hash

字典 结构:

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;  // 头插,开链法解决冲突
} dictEntry;
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;    // hash表
    unsigned long size; // bucket数量
    unsigned long sizemask;
    unsigned long used; // 已使用的key数量
} dictht;


typedef struct dict {
    dictType *type;  // 函数实现
    void *privdata;   // 数据
    dictht ht[2];   // ht[1]用于rehash期间的临时使用
    long rehashidx; /* rehashing 没有在进行时rehashidx == -1,正在rehash表示rehash进行到的old table的idx
 */
    unsigned long iterators; /* number of iterators currently running,dict扫描的safe iterator,等于0的时候才能进行rehash */
} dict;

struct dict为redis的hash结构实现,使用双buffer(ht[2])解决rehash问题,dictht中,table为线型表,包含了size个bucket,每个bucket里面是一个dictEntry结构,dictEntry中next为指向同bucket下一个元素的指针,可以发现redis的hash表解决冲突方法为开链法

添加元素

// 如果key已经存在了,返回NULL,用existing表示这个key所在的entry
// key不存在,添加上去,并且返回这个新添加key的entry
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    dictht *ht;


    if (dictIsRehashing(d)) _dictRehashStep(d);  // 如果正在rehash


    /* Get the index of the new element, or -1 if
     * the element already exists. */
    // 获取新key的index,如果这个key已经存在了返回-1
    // 在_dictKeyIndex中可能会启动expand
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;


    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. */
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];  // 如果hash表正在rehash,就使用d->ht[1],否则使用d->ht[0]
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index]; // 新元素插到头部去
    ht->table[index] = entry;
    ht->used++;


    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;
}

增量rehash

在添加元素过程中,如果发现hash表的used/size >= 1了,那么会启动rehash。hash表使用增量rehash的方式,每次调用添加或查找元素的函数会执行rehash一部分元素(比如1个);新hash表的table每次以*2扩充

增量rehash的执行过程:

// 增量rehash,将rehash向前推进n步。n=1.
// 返回1表示rehash没有完成,返回0表示本次rehash全部完成并切换到ht[0]了
int dictRehash(dict *d, int n) {  
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    if (!dictIsRehashing(d)) return 0; // 没有在rehash,直接返回


    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;


        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        while(d->ht[0].table[d->rehashidx] == NULL) {   // 遇到空的bucket跳过,遇到10个空的bucket之后直接返回
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
        de = d->ht[0].table[d->rehashidx];  // 指向bucket=d->rehashidx的第一个节点
        /* Move all the keys in this bucket from the old to the new hash HT */
        // 老hash表的d->rehashidx个bucket所有节点重新hash到新hash表 
        while(de) {
            uint64_t h;


            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;  // 获取在新hash表中的index
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de; 
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;  // 老hash表的节点指向null
        d->rehashidx++;
    }


    /* Check if we already rehashed the whole table... */
    // 切换ht,仍然ht[0]为可用table
    // 释放ht[0]的table并重新指向ht[1]的table,ht[1]的table指向null
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1];
        _dictReset(&d->ht[1]);
        d->rehashidx = -1;
        return 0;
    }


    /* More to rehash... */
    return 1;
}

查找元素

dictEntry *dictFind(dict *d, const void *key)
{
    dictEntry *he;
    uint64_t h, idx, table;


    if (dictSize(d) == 0) return NULL; /* dict is empty */
    if (dictIsRehashing(d)) _dictRehashStep(d);
    h = dictHashKey(d, key);
    // 在dictAddRaw中,如果正在rehash,那么直接往ht[1]添加元素,所以新添加元素在ht[0]没有,要同时查找ht[0]和ht[1]
    for (table = 0; table <= 1; table++) {  
        idx = h & d->ht[table].sizemask;
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) return NULL;  // 没有在rehash的话,就不找ht[1]了
    }
    return NULL;
}