【Redis实战】认识Redis中的全局哈希表

Redis是如何支持基于Key的快速访问的

一谈到Redis,马上能想到的就是:“快”,那么,Redis之所以快,一方面是因为Redis的所有操作都在内存中完成,内存操作本身就很快,另一方面就要归功于它的数据结构了,高效的数据结构是Redis快的基石。

全局哈希表

为了实现基于Key的快速访问,Redis采用了哈希表作为最底层的数据存储结构,如果你了解Java中的HashMap,那么理解Redis的哈希表则应该非常容易,实际上也就是数组+链表的结构,这样一来,我们就可以在O(1)的时间复杂度下快速的查找出所需的Key,并且无论是多少Key都不受影响。

我们先来看看哈希表的具体结构设计,每一个dictEntry元素,都保存了*key和*value的指针,*value指针保存在联合体v中,既然保存的是指针,所以也就能保存任何类型的数据结构(String,Hash,List,Set)

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

哈希表结构

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

哈希冲突

当然,提到哈希表,肯定少不了哈希冲突的问题,Redis中采用的方式和Java中的HashMap一样,都是链式哈希的方式,所以一旦冲突变多,就会导致链表过长,最终退化为O(n)的时间复杂度,这对Redis来说肯定是不能接受的。

所以,解决的方式也和Java中的HashMap一样,直接进行一次rehash操作就好了,简单来说就是扩大Entry数组的大小,从而来减少哈希冲突,当然为了不对线上访问造成影响,Redis还采用渐进式rehash的方式,实际上Redis每次在进行rehash操作时,会新准备一个比原哈希表大一倍的新哈希表,然后在每一次处理请求的时候,顺便处理一次数据迁移,比如从原哈希表的第一个索引位置开始,把这个位置上的Entry全部挪到新的哈希表中,这样通过分摊处理,就避免了一次性全量处理所带来的阻塞问题。

一张图

在这里插入图片描述

相关源码

就用Hash数据类型中的HGET方法举例

void hgetCommand(client *c) {
    robj *o;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL ||
        checkType(c,o,OBJ_HASH)) return;

    addHashFieldToReply(c, o, c->argv[2]->ptr);
}
static void addHashFieldToReply(client *c, robj *o, sds field) {
    int ret;

    if (o == NULL) {
        addReplyNull(c);
        return;
    }
	
	// 如果底层数据结构是压缩列表的处理逻辑
    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
        if (ret < 0) {
            addReplyNull(c);
        } else {
            if (vstr) {
                addReplyBulkCBuffer(c, vstr, vlen);
            } else {
                addReplyBulkLongLong(c, vll);
            }
        }
	
	// 如果底层是Hash表的处理逻辑
    } else if (o->encoding == OBJ_ENCODING_HT) {
        sds value = hashTypeGetFromHashTable(o, field);
        if (value == NULL)
            addReplyNull(c);
        else
            addReplyBulkCBuffer(c, value, sdslen(value));
    } else {
        serverPanic("Unknown hash encoding");
    }
}

看Hash表的逻辑,基本上就是标准的Hash表查找方式

sds hashTypeGetFromHashTable(robj *o, sds field) {
    dictEntry *de;

    serverAssert(o->encoding == OBJ_ENCODING_HT);

    de = dictFind(o->ptr, field);
    if (de == NULL) return NULL;
    return dictGetVal(de);
}
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);
    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;
    }
    return NULL;
}
#define dictGetVal(he) ((he)->v.val)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值