百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

阿里面试官:谈谈对Redis哈希表的理解

mhr18 2024-11-14 16:25 24 浏览 0 评论

不少朋友问我,能不能搞个八股文精讲,把面试问题讲讲透,于是系列就这样诞生了。咱们第一期先聊聊Redis。

相信哈希表大家并不陌生,今天顺便聊聊Redis的哈希表。

Hash表回顾

哈希表是一种存储数据的结构,它有很多名字(键值对、字典、符号表、映射、关联数组)。在哈希表中,键和值是一一对应的关系,一个键key对应一个值value。哈希表这个数据结构可以通过键key,在O(1)时间复杂度的情况下获得对应的值。

由于C语言自己没有内置哈希表这一数据结构,因此Redis自己实现了Hash表。

哈希冲突及处理办法

哈希表最关键的问题就在于哈希冲突。即,两个项,经过哈希函数计算,发现其对应的存储方式位置一致。对于这种情况,就需要进行进一步处理了。

解决哈希冲突的办法

大家应该背过我写的数据结构与算法八股文背诵版,还记得解决Hash冲突的方法嘛。

线性探查法(开放地址)。

这个方法的核心是:一旦碰见有冲突,该项往后顺延.

来看个例子吧。

  1. 按hash算法,新键值对应该存在箭头所处位置,可惜该位置有值了:

开放地址法

  1. 因此需要存储顺延的位置:

开放地址法

  1. 顺延位置也有值了,再往后顺延

开放地址法

  1. 顺延位置还是有值,再往后顺延,终于存储上了

开放地址法

链地址法(拉链法)

Redis采用的方法就是这种拉链法。来看下面例子。新键值对计算应该存到二号,二号此时已经有一个键值对了。因此,直接通过链表的方式挂到二号键值对1的下面。

拉链法

对于新的键值对也是如此,通过链表的方式挂到二号键值对2的下面。

Rehash

在讲rehash之前,首先需要引入一个定义:负载因子。来看一下负载因子的定义吧:

负载因子 = 散列表内元素个数/散列表的长度

如果负载因子高,就说明哈希冲突概率大,这样会严重拖慢查找效率。

如果负载因子低,就说明这哈希表好像占用空间太多了,大部分空间都没元素。

为了使负载因子值在合理范围内,程序需要对哈希表进行扩展或收缩。由于空间变大或缩小,之前的键在老表的存储位置,在新表中就不一定一样了,需要重新计算。这个重新计算,并把老表元素转移到新表元素的过程就叫做rehash。当然无论是java中的hashmap,concurrenthashmap,还是今天要讲的Redis哈希表,都涉及rehash过程。

Redis中哈希表的数据结构

来看一下Redis的Hash表逻辑设计结构 Redis的哈希表主要由三个结构构成:

  1. dictht。单纯表示一个哈希表
  2. dictEntry。哈希表的一项,可以看作就是一个键值对
  3. dict。Redis给外层调用的哈希表结构,包含两个dictht
typedef struct dictht { 
    dictEntry **table; //哈希表数组(哈希表项集合)
    unsigned long size; //Hash表大小 
    unsigned long sizemask; //哈希表掩码
    unsigned long used;//Hash表已使用的大小
} dictht;

稍微解释一下各个项。

  1. table:哈希表项的指针数组
  2. size:哈希表大小,这应该不用多解释吧
  3. sizemask:掩码。这个值其实设计思想很棒,假设Redis长度是3,你想访问第5个元素,如果按之前的方法,那肯定是访问到超出redis哈希表范围的地址空间了。所以redis规定,你想访问元素,先把index与size做好,把超过redis长度的部分就截断了,就不会发生内存安全问题。
  4. Hash表已使用的大小。不解释。

讲了Hash表,来看看哈希项

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

我们知道,Redis采用拉链法解决哈希冲突的问题。因此,Redis的哈希表项就有一个next指针,指向下一个元素,通过该指针,就可以访问多个具有相同哈希值的键值对。

最后我们来看看dict结构。

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    int reshaidx;
} dict;

大家肯定很好奇,好好地dict,搞两个哈希表做啥?当然也有不好奇的小伙伴,但没办法,架不住面试官也很好奇啊。

答案揭晓,两个hash表是为了rehash。

那什么情况下需要rehash呢?

  • 如果redis没在执行后台备份,当负载因子大于等于1就执行。(反正CPU闲着也是闲着)
  • 如果redis在执行后台备份,当负载因子大于等于5就执行。(CPU在干备份了,咱对于实在挤的表改一改,等CPU闲下来,再把稍微偏挤的rehash)

我们来看一下如果出现需要rehash的情况,需要的执行步骤:

  1. 分配空间给ht[1]。分配空间由ht[0]的具体参数决定。
  2. 将ht[0]存储的键值对,重新计算hash值和索引值,并赋值到ht[1]的对应位置中。
  3. 当赋值完成后,释放ht[0]所占用空间,并把ht[0]指向ht[1]目前的地址。
  4. ht[1]指向空表。

渐进式rehash

由于步骤二采用的计算方式如果在一定时间做,占用资源过高,所以redis提出了渐进式rehash的方式。拿大白话来讲,就是原来是一次,一次性的搬运,现在变成了分批搬运。

在分批搬运的过程中,难免会收到其他各式各样的请求。

  1. 对于写请求,即往redis哈希表增加新的键值对时,redis会把数据直接存放到ht[1]表中。
  2. 对于查请求,即查询特定键对应的值时,redis首先会在ht[0]中查找,如果查找失败,就会在ht[1]表中查找。
  3. 对于更新请求,redis首先会在ht[0]中查找,如果查找失败,就会在ht[1]表中更新。
  4. 对于删除请求,redis首先会在ht[0]中查找,如果查找失败,就会在ht[1]表中删除。

相关推荐

说说Redis的单线程架构(redis的单线程模型)

一句话总结Redis采用单线程处理命令请求,避免了多线程的上下文切换和锁竞争,保证原子性操作。其基于内存的高效执行和I/O多路复用模型支撑了高并发性能。网络I/O和持久化操作(如RDB/AOF)由后台...

答记者问之 - Redis 的高效架构与应用模式解析

问:极客程序员你好,请帮我讲一讲redis答:redis主要涉及以下核心,我来一一揭幕Redis的高效架构与应用模式解析Redis是一个开源的内存数据存储系统,因其高性能、丰富的数据结构和易用性...

Redis的5种核心数据结构,及其最经典的“应用场景”

Redis凭什么稳坐缓存界头把交椅?全靠这五个“身怀绝技”的数据结构!在分布式系统的江湖里,Redis就像一位身怀绝技的武林高手,而它的五大核心数据结构正是克敌制胜的五套绝学。今天咱们就来拆解这些独门...

精准定位文件包含漏洞:代码审计中的实战思维

前言最近看到由有分析梦想cms的,然后也去搭建了一个环境看了一看,发现了一个文件包含漏洞的点,很有意思,下面是详细的复现和分析,以后代码审计又多了一中挖掘文件包含漏洞的新思路环境搭建下载https...

ARDM:一款国产跨平台的Redis管理工具

ARDM(AnotherRedisDesktopManager)是一款免费开源的Redis桌面管理客户端,支持Windows、Mac、Linux跨平台。功能特性ARDM提供的主要功能如...

SpringBoot的Web应用开发——Web缓存利器Redis的应用!

 Web缓存利器Redis的应用Redis是目前使用非常广泛的开源的内存数据库,是一个高性能的keyvalue数据库,它支持多种数据结构,常用做缓存、消息代理和配置中心。本节将简单介绍Redis的使...

Windows服务器部署CRMEB开源电商系统,详细教程来了!

安装PHP已经安装过PHP的可以跳过首先安装VC运行库下载地址https://docs.microsoft.com/zh-cn/cpp/windows/latest-supported-vc-redi...

Windows系统下Redis各个安装包介绍与选择指南

简介Redis作为高性能的键值数据库,广泛应用于缓存、消息队列等场景。在Windows系统中部署Redis时,用户可以选择多种安装包以满足不同的需求。本文将详细介绍以下Redis8.0.3版本的安装...

从面试题入手,深度剖析Redis Cluster原理

揭开RedisCluster的神秘面纱**在当今数字化浪潮中,数据量呈爆炸式增长,应用程序对数据存储和处理的要求也日益严苛。Redis作为一款高性能的内存数据库,凭借其出色的读写速度和丰富的数...

给大家推荐些好的c语言代码的网站

C语言,那就来推荐几个吧,部分含有C++:1、TheLinuxKernelArchives(kernel.org)Linux内核源码,仅限于C,但内核庞大,不太适合新手;2、redis(redi...

Redis String 类型的底层实现与性能优化

RedisString是Redis中最基础也是应用最广泛的数据类型,它能存储文本、数字、二进制数据等多种形式的信息。深入理解其底层实现对构建高性能分布式系统至关重要。Redis字符串的底层结...

阿里面试问:Redis 为什么把简单的字符串设计成 SDS?

分享了一道面阿里的redis题,我看了以后觉得挺有意思。题目大致是这样的面试官:了解redis的String数据结构底层实现嘛?铁子:当然知道,是基于SDS实现的面试官:redis是用C语言开发的,那...

编程语言那么多,为何C语言能成为最成功的语言?

编程语言那么多,为何C语言能成为最成功的语言?2025年嵌入式岗位暴增47%,新人却还在问"C语言过时了吗"。真相是连机器人关节驱动都得靠它写,不会指针连芯片手册都看不懂。见过用Pyt...

go-zero 使用 redis 作为 cache 的 2 种姿势

在go-zero框架内,如在rpc的应用service中,其内部已经预置了redis的应用,所以我们只需要在配置中加入相关字段即可,另外,在svcContext声明redisc...

Redis事务深度解析:ACID特性、执行机制与生产实践指南

一、Redis事务的本质与核心机制Redis事务通过MULTI、EXEC、WATCH等命令实现,其本质是将多个命令序列化后一次性执行,而非传统数据库的严格事务模型。核心特点如下:命令队列化:MULT...

取消回复欢迎 发表评论: