redis-6.0.8-dict的一些杂谈

本文解析了Redis中哈希表dict的数据结构,包括ht属性、rehashing过程,并展示了如何从客户端输入解析argc和argv。同时介绍了robj结构体与SDS内存模型,以及哈希表的扩容与缩容策略。

数据结构

typedef struct dict {
    dictType *type; //函数指针
    void *privdata;
    /*ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下, 字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用*/
    dictht ht[2];   //hash表
    /*它记录了rehash目前的进度,如果目前没有在进行rehash,那么它的值为-1*/
    long rehashidx; /* rehashing not in progress if rehashidx == -1 重新排列*/      //数据量特别大时一个数组槽位一个数组槽位的移动
    unsigned long iterators; /* number of iterators currently running */
} dict;

typedef struct dictht {
    dictEntry **table;      //哈希表,哈希冲突通过链表进行连接
    unsigned long size;         //哈希表大小
    unsigned long sizemask; //哈希表大小掩码,用于计算索引值。总是等于size-1
    unsigned long used;      //该哈希表已有节点的数量
} dictht;

typedef struct dictEntry {
    void *key;  //key值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct redisObject {//value结构体
    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). */ // LRU时钟
    int refcount;       //引用计数
    void *ptr;  // 指向数据的指针。当robj存储的数据可以使用long类型表示的时候,数据直接存储在ptr字段
    //当使用OBJ_ENCODING_EMBSTR编码时,ptr指向sdshdr8的flags字段之后
} robj;




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 */ //标记SDS_TYPE_8
    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[];
};

请求数据解析argc,argv

void readQueryFromClient(connection *conn) {
...
 nread = connRead(c->conn, c->querybuf+qblen, readlen);
 printf("c->querybuf_peak=%ld,qblen=%ld,nread=%d,c->querybuf+qblen=%s",c->querybuf_peak,qblen,nread,c->querybuf+qblen);
...
}

调用命令

127.0.0.1:6379> set abcd aa
OK

打印结果如下:

c->querybuf_peak=0,qblen=0,nread=31,c->querybuf+qblen=*3
$3
set
$4
abcd
$2
aa
对c->querybuf中的数据解析出参数argc和argv
void processInputBuffer(client *c) {
...
if (c->reqtype == PROTO_REQ_MULTIBULK) {	//redis-cli命令请求的协议类型
            if (processMultibulkBuffer(c) != C_OK) break;   //解析read出来的参数
}
...
}
int processMultibulkBuffer(client *c) {
...
 serverAssertWithInfo(c,NULL,c->querybuf[c->qb_pos] == '*'); //*2表示后面有2个参数,*3表示后面有3个参数
 ok = string2ll(c->querybuf+1+c->qb_pos,newline-(c->querybuf+1+c->qb_pos),&ll);
...
 c->multibulklen = ll;//参数数量
 /* Setup argv array on client structure */
 if (c->argv) zfree(c->argv);
 c->argv = zmalloc(sizeof(robj*)*c->multibulklen);
...
while(c->multibulklen) {    //参数数量
...
             if (c->querybuf[c->qb_pos] != '$') {
                addReplyErrorFormat(c,
                    "Protocol error: expected '$', got '%c'",
                    c->querybuf[c->qb_pos]);
                setProtocolError("expected $ but got something else",c);
                return C_ERR;
            }

            ok = string2ll(c->querybuf+c->qb_pos+1,newline-(c->querybuf+c->qb_pos+1),&ll);
            ....
            c->bulklen = ll;    //参数长度 $5表示5个字节,$7表示7个字节
...
 c->argv[c->argc++] =createStringObject(c->querybuf+c->qb_pos,c->bulklen);	//加入到参数argv里面
 ...
	}
}

robj结构体与sdshdr8、sdshdr16、sdshdr32结构体

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

其中createEmbeddedStringObject为创建一整片内存,即robj结构体和ptr指针都在一片内存当中。createRawStringObject为创建两片内存,robj和ptr指向的内存为不同内存片。

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

在这里插入图片描述

robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}

在这里插入图片描述
sds类型内存模型如下:
在这里插入图片描述
其中关键的宏定义和函数定义如下:

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

static inline size_t sdslen(const sds s) {      //已使用的内存
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

static inline size_t sdsavail(const sds s) {    //剩余内存
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5: {
            return 0;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            return sh->alloc - sh->len;
        }
    }
    return 0;
}

哈希表dict的扩容和缩容

int dictResize(dict *d) //哈希表缩容
{
    unsigned long minimal;

    if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
    minimal = d->ht[0].used;
    if (minimal < DICT_HT_INITIAL_SIZE)
        minimal = DICT_HT_INITIAL_SIZE;
    return dictExpand(d, minimal);
}
int dictExpand(dict *d, unsigned long size) //哈希表进行扩容
{
    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; /* the new hash table */
    /*
    1.如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n。      例如ht[0].used为5,ht[0].used*2=10,第一个大于的2^n次方为2^4即16。
    2.如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n。        例如ht[0].used为5,ht[0].used=5,第一个大于的2^n次方为2^3即8。
    */
    unsigned long realsize = _dictNextPower(size);  

    /* Rehashing to the same table size is not useful. */
    if (realsize == d->ht[0].size) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

其中哈希表dict包含两个哈希表项在通过key进行查找时ht[0]和ht[1]都会进行查找匹配表项中的key值。在int dictRehash(dict *d, int n)函数中每次移动哈希表中的一个n个槽位,从从ht[0]哈希表上重新哈希到ht[1].table[h]位置。
当ht[0]中已使用节点数为0时,将ht[1]赋值给ht[0]即d->ht[0] = d->ht[1];随后ht[1]进行复位,为下次扩容缩容做好准备。

int dictRehash(dict *d, int n) {    //挪动数据,n表示几个槽位   扩容缩容时同时通过key进行find时哈希表1和哈希表2同时进行查找
    int empty_visits = n*10; /* Max number of empty buckets to visit. 最大访问空桶数*/
    if (!dictIsRehashing(d)) return 0;

    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) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        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;
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de; //de节点从ht[0]哈希表上重新哈希到ht[1].table[h]位置
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    /* Check if we already rehashed the whole table... */
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1];
        _dictReset(&d->ht[1]);      //哈希表1复位
        d->rehashidx = -1;
        return 0;
    }

    /* More to rehash... */
    return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值