高级数据结构——hash表与布隆过滤器

文章目录

  • hash表与布隆过滤器
    • 1. hash函数
    • 2. 选择hash函数
    • 3. 散列冲突
      • 3.1 负载因子
      • 3.2 冲突解决
      • 3. STL中的散列表
    • 4. 布隆过滤器
      • 4.1 背景
        • 1. 应用场景
        • 2. 常见的处理场景:
      • 4.2 布隆过滤器构成
      • 4.3 原理
      • 4.4 应用分析
      • 4.5 要点
    • 5. 分布式一致性hash
      • 5.1 缓存失效问题
    • 6. 大数据相关的面试题
    • 学习参考

hash表与布隆过滤器

本文讲述了hash表的原理与实现、hash冲突及解决方法、基于位图和hash函数实现的布隆过滤器、以及用于负载均衡的分布式一致性hash等。

1. hash函数

用来求hash值的一种映射函数,hash函数可能会把两个或者两个以上的不同key映射到同一地址,这种情况称之为冲突(或者hash碰撞);

与平衡二叉树比较

  • 平衡二叉树利用二分查找的思维,通过每次比较排除一半元素来达到快速查找的目的。时间复杂度为O(logn)。10万结点比较20次,10亿结点比较30次即可。
  • 散列表,通过简历key值到索引位置的映射关系来实现快速查找,实现这种映射的函数叫做哈希函数(或者散列函数),平均时间复杂度为O(1)。

2. 选择hash函数

  • 计算速度快
  • 强随机分布(等概率、均匀地分布在整个地址空间)
  • murmurhash1, murmurhash2, murmurhash3, siphash(redis6.0当中使用,也是rust等大多数语言选用的hash算法来实现hashmap),cityhash都具备强随机分布性。
  • siphash主要解决相近的字符串的随机分布性。
  • murmurhash2是使用最频繁的hash算法。

测试地址:https://github.com/aappleby/smhasher

3. 散列冲突

不同的key可能会被映射到同一个地址,这种情况就叫做散列冲突。

3.1 负载因子

​ 负载因子:是指哈希表中已存储元素的数量与哈希表中桶(bucket)数量之间的比例,能够描述存储密度或者散列冲突的激烈程度。

3.2 冲突解决

如果负载因子在合理范围内

  • 拉链法

    将冲突元素用一个链表链接起来,查找时在链表内进行线性查找。

    这样可能出现一种极端情况,当冲突的元素比较多,冲突链表过长时,会显著影响查询性能,这时可以将这个链表转换为红黑树、最小堆。可以采用超过256(经验值)个结点的时候将链表结构转换为红黑树或者堆结构。

  • 开放寻址法

    • 线性探查法:有hash聚集(也叫主聚集)问题,即当多个元素发生hash冲突后,会被集中插入到hash表中的相邻位置,从而导致后续的冲突更容易发生在这些已占用的区域,形成聚集效应。这样新插入的元素需要花费更多的时间探查空闲位置,最终导致插入和查找的效率下降。
    • 二次探查法,使用非线性方式进行探查,次聚集问题,即由于探查模式相同而导致不同的键分布在相邻的位置上。
    • 再散列法,相比线性探查法就能够使插入的结点分布更加均匀,次聚集问题。

如果负载因子不在合理范围内

  • 扩容:翻倍扩容
  • 缩容

然后进行rehash

3. STL中的散列表

  • unordered_map, unordered_set, unordered_multimap, unordered_multiset

在这里插入图片描述

  • STL中的hash表采用类似拉链法的方法解决散列冲突。不同的是,为了实现迭代器,STL中会将每个桶对应的链表串连起来,这样所有的键值对就被组织成了一个单链表,方便迭代器的实现。这种情况下,为了实现头插法,每个桶中保存的是上一个拉链的尾结点。

以下是STL中插入一个结点时的代码实现,每个桶中的指针指向的的是另一个桶中的最后一个结点。

 if (_M_buckets[__bkt]){// Bucket is not empty, we just need to insert the new node// after the bucket before begin.__node->_M_nxt = _M_buckets[__bkt]->_M_nxt;_M_buckets[__bkt]->_M_nxt = __node;}
else
{// The bucket is empty, the new node is inserted at the// beginning of the singly-linked list and the bucket will// contain _M_before_begin pointer.__node->_M_nxt = _M_before_begin._M_nxt;_M_before_begin._M_nxt = __node;if (__node->_M_nxt)// We must update former begin bucket that is pointing to// _M_before_begin._M_buckets[_M_bucket_index(__node->_M_next())] = __node;_M_buckets[__bkt] = &_M_before_begin;
}

4. 布隆过滤器

布隆过滤器(Bloom Filter)是一种基于哈希函数和哈希表实现的空间效率非常高的概率型数据结构,用于测试一个元素是否是一个集合的成员。它有一个重要的特点:可以判断元素是否属于某个集合,但不能确定元素不属于该集合。简单来说,布隆过滤器允许有一定的误判率,但不会漏判。

4.1 背景

1. 应用场景

内存有限,只想确定某个key是否存在,不想知道具体内容。

布隆过滤器通常用于判断某个key一定不存在的场景,同时允许判断存在时由误差的情况。

过滤对不存在的值的查询

  • 某个文件,如果要查询其中某条记录,可以先通过布隆过滤器判断该记录是否存在。
  • 某个数据库,在执行查询之前可以先通过布隆过滤器判断目标是否存在。
2. 常见的处理场景:
  1. 缓存穿透

在这里插入图片描述

缓存穿透的概念:

缓存穿透是指当查询的数据既不在缓存中也不在数据库中时,缓存系统没有对这些无效请求进行有效过滤,导致请求直接穿透缓存,频繁访问数据库,从而给数据库带来巨大的压力。这种情况通常出现在分布式系统中,特别是在使用缓存(如 Redis、Memcached)来优化数据访问的场景。

缓存穿透的成因:

  1. 恶意攻击:攻击者通过大量查询不存在的数据,绕过缓存系统,直接将请求压向数据库。这种行为可能会使数据库负载过大,影响系统性能。
  2. 自然请求:如果系统没有针对查询条件做有效过滤,用户可能请求不存在的数据(如查询一个从未有过的ID),也会直接穿透缓存,访问数据库。

缓存穿透的解决:

  1. 缓存空值,当某个键查询不到数据时,可以将空结果也存入缓存,并设置一个较短的过期时间。这样即使再次查询同样不存在的键,也会直接返回缓存中的空值,避免穿透到数据库。
  2. 布隆过滤器,使用布隆过滤器对所有可能存在的数据进行预先判断。在访问缓存之前,首先通过布隆过滤器判断查询的键是否可能存在。如果布隆过滤器判断该数据不可能存在,则直接返回空值,避免查询数据库。

总结:

缓存穿透是缓存系统中的一个常见问题,主要是由于缓存没有有效过滤无效请求,导致数据库承受过大的负载。通过缓存空值、使用布隆过滤器、参数校验等方法可以有效减少缓存穿透,提升系统整体性能。

  1. 热key限流

热key:

热Key指的是在缓存中某些特定键被大量并发请求访问的现象。因为请求集中于少数几个Key,缓存服务器的负载会非常高,当缓存失效时,可能会导致大量请求穿透到数据库,从而引发更大的压力,甚至导致数据库宕机。

热Key的影响:

  1. 缓存节点压力集中:缓存系统是分布式的,但由于热Key的访问量远超其他Key,某些缓存节点可能会成为性能瓶颈,导致这些节点比其他节点负载严重失衡。
  2. 缓存失效冲击数据库:在缓存失效时,所有的请求瞬间落到数据库,数据库处理不过来,可能会导致整个系统崩溃。

热key限流策略:

  1. 请求合并(Batching):当大量请求访问某个热Key且缓存过期时,可以使用请求合并策略,只让第一个请求去加载数据,其他请求等待第一个请求的结果。这种方式可以有效避免瞬间大量请求同时去查询数据库。

  2. 限流保护:对于访问量过高的Key,可以对其进行限流,控制每秒对该Key的最大请求次数令牌桶算法漏桶算法常被用于限流实现。

  3. 多副本缓存:在不同的缓存节点上放置同一个热Key的数据,这样可以将访问压力分散到多个缓存节点上。可以通过一致性哈希算法和副本策略来实现。

  4. 数据预热:在某些特定场景中(如系统重启、缓存失效后),可以提前预加载一些热点数据到缓存中,避免缓存初始化时发生大量请求直接穿透到数据库。

  5. 动态调整TTL:对于热Key,可以设置相对较长的缓存过期时间(TTL),这样可以避免频繁的缓存失效和更新。甚至可以根据热Key的访问频率动态调整其TTL,使得热Key在高峰期保持长时间不失效。

  6. 降级策略:在极端情况下,可以采用降级策略,对于热Key的请求进行降级处理,例如返回默认值或静态页面,确保系统的可用性不受单个热Key的影响。当缓存和数据库压力都很大时,可以设置简单的降级逻辑,避免系统崩溃。

总结:

热Key限流是一种应对高频访问缓存键的策略,主要目的是为了避免缓存过载和缓存失效后对数据库的冲击。常见的限流措施包括请求合并、限流保护、多副本缓存、数据预热和分级缓存等,这些方法能够有效减轻系统负担,保证系统的稳定性和高可用性。

4.2 布隆过滤器构成

布隆过滤器由一个位示图和k个哈希函数构成。位图的大小m和哈希函数的个数k由预期存储的最大元素数和最大假阳率决定。

  • 位图

在这里插入图片描述

  • n个hash函数

4.3 原理

在这里插入图片描述

  • 如何判断某个key不存在?

    bitmap[hash(key) % bit_size] == 0 ? 只要一个位置为0,就说明不存在。

  • 如果判断某个key存在?

    无法判断某个key一定存在,只能判断某个key一定不存在。

  • 布隆过滤器不支持删除操作

    bitmap中的一个位可能对应多个key

  • 假阳率?

    布隆过滤器判断一个key存在,但实际上不存在的情况。

4.4 应用分析

  • n,预期存入的元素个数
  • p,最大的假阳率,即判断hash值对应的位置都为1,判断为存在,而实际不存在的情况占比
  • m,位图的大小
  • k,hash函数的个数

从n和p计算m和k:https://hur.st/bloomfilter/

在这里插入图片描述

结论:当k=31时,假阳率(hash冲突率)是最低的。

4.5 要点

  • 能确定一个key一定不存在,可控假阳率确定存在

  • 不能删除

  • 先根据n和p算出m和k

5. 分布式一致性hash

一致性哈希的核心思想是通过将数据和节点映射到一个环形的哈希空间中。每个数据和节点都通过哈希函数映射到这个空间,然后通过哈希值来确定数据与节点的对应关系。

5.1 缓存失效问题

假设要对请求做负载均衡,原理是通过hash函数将请求的key值映射到一台缓存服务器中,因为hash值的分布是均匀的,所以每台服务器的负载应该也是均匀的。但是当服务器的数量发生变化时,就会导致所有请求的映射关系都被破坏,所有服务器中的缓存全部失效。

在这里插入图片描述

解决办法

引入哈希环,将数据和服务器结点都映射到该环上,距离数据顺时针方向上最近的服务器结点保存该节点的缓存。

  • 固定算法 hash(key) % 2 ^ 32 = index
  • hash迁移,改变节点的映射关系,解决大面积缓存失效,变为局部缓存失效
  • 增加虚拟结点,使迁移数据量减少

如图所示,在哈希环上,每个数据被映射到顺时针方向距离其最近的服务器结点。

在这里插入图片描述

  1. hash偏移

该问题是指当服务器结点较少时,可能服务器结点的间隔十分不均匀,导致服务器负载不均衡。

如图所示,s1服务器结点负载相对过大。
在这里插入图片描述

解决办法

增加虚拟结点,即一台服务器可以对应多个服务器结点,这样就增加了总的服务器结点数量,使各个服务器分布的更加均匀。

6. 大数据相关的面试题

在这里插入图片描述
题目
只用2G内存找到20亿个整数中出现次数最多的数

  • 思路:将大文件用hash拆成小文件,确保相同得key值被放入同一个文件(映射),并且每个每个文件中得数据量大致相同(利用其强随机特性)。
  • 单台机器通过hash分流到多台机器

学习参考

学习更多相关知识请参考零声 github。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/472832.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xcode-select: error: tool ‘xcodebuild‘ requires Xcode, but active developer

打开 .sh 文件所在的终端窗口,执行终端命令:sh 文件名.sh,出现如下错误: 解决办法:

java中volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?

大家好,我是锋哥。今天分享关于【java中volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?】面试题。希望对大家有帮助; java中volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗&…

candence : 通孔焊盘、插装器件封装绘制

通孔焊盘、插装器件封装绘制 以2.54mm 2x10的 排针为例绘制封装 一、 flash 热风焊盘制作 1、新建 2、选择 flash SYMBOL,并设置保存路径 3、add flash 具体参数 花焊盘参数 Inner diameter 通孔直径(1.0mm) 圆形补偿值(0.4mm)1.4mm Outer diameter 通孔直径…

VSCode设置

打开设置页 VSCode打开配置页面,有多种方式: a. 点击左上角 File(文件) -> Preferences (首选项) -> Settings(设置)。 b. 使用快捷键 Ctrl ,(Windows) 或 Cmd ,(Mac)。 c. 点击左下角 Manage(管理) -> Settings(设置)。 VSCode设置页面打…

SpringMVC数据校验、数据格式化处理、国际化设置

SpringMVC数据校验、数据格式化处理、国际化设置 1.数据验证 (1)使用JSR-303验证框架 JSR(Java Specification Requests),意思是Java 规范提案。JSR-303是JAVA EE 6中的一项子规范,叫做Bean Validation。JSR 303&am…

加速 AI 创新:引入 Elastic AI 生态系统

作者:来自 Elastic Alyssa Fitzpatrick, Steve Kearns 生成式人工智能 (Generative AI - GenAI) 正在改变我们所熟知的商业格局。为了简化和加速开发人员构建和部署检索增强生成 (retrieval augmented generation - RAG) 应用程序的方式,Elastic 自豪地宣…

SpringSecurity 鉴权认证入门讲解

​ Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。 ​ 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与Sp…

# ubuntu 安装的pycharm不能输入中文的解决方法

ubuntu 安装的pycharm不能输入中文的解决方法 一、问题描述: 当在 ubuntu 系统中,安装了 pycharm(如:pycharm2016, 或 pycharm2018),打开 pycharm 输入代码时,发现不能正常输入中文,安装的搜狗…

小白进!QMK 键盘新手入门指南

经常玩键盘的伙伴应该都知道,现在的键盘市场可谓是百花齐放,已经不是之前的单一功能产品化时代。我们可以看到很多诸如:机械轴键盘、磁轴键盘、光轴键盘、电感轴键盘,以及可能会上市的光磁轴键盘,更有支持屏幕的、带旋…

EXCEL 或 WPS 列下划线转驼峰

使用场景: 需要将下划线转驼峰,直接在excel或wps中第一行使用公式,然后快速刷整个列格式即可。全列工下划线转为格式,使用效果如下: 操作步骤: 第一步:在需要显示驼峰的一列,复制以…

微信小程序:vant组件库安装步骤

前言:在微信小程序中引用vant组件报错,提示路径不存在,这很有可能是因为没有安装构建vant组件库导致。下面是我整理的安装vant组件库的步骤: 第一步:安装node.js(执行完第一步请重启小程序) 具体步骤请看链接:node.js…

vue3【实战】切换全屏【组件封装】FullScreen.vue

效果预览 原理解析 使用 vueUse 里的 useFullscreen() 实现 代码实现 技术方案 vue3 vite UnoCSS vueUse 组件封装 src/components/FullScreen.vue <template><component:is"tag"click"toggle":class"[!isFullscreen ? i-ep:full-sc…

GPIO相关的寄存器(重要)

目录 一、GPIO相关寄存器概述 二、整体介绍 三、详细介绍 1、端口配置低寄存器&#xff08;GPIOx_CRL&#xff09;&#xff08;xA...E&#xff09; 2、端口配置高寄存器&#xff08;GPIOx_CRH&#xff09;&#xff08;xA...E&#xff09; 3、端口输入数据寄存器&#xff…

【Hadoop实训】Hive 数据操作②

延续上一篇文章&#xff0c;不懂的宝子们请看以下链接&#xff1a; 【Hadoop实训】Hive 数据操作①-CSDN博客 目录 一、Group by 语句 (1)、计算emp表每个部门的平均工资 (2)、计算emp表每个部门中每个岗位的最高工资 二、Having 语句 (1)、求每个部门的平均工资 (2)、求每个…

Vulhub漏洞复现---solr---CVE-2019-17558

漏洞描述 Apache Solr 5.0.0到Apache Solr 8.3.1容易受到通过VelocityResponseWriter执行的远程代码的攻击。Velocity模板可以通过configset ’ Velocity / 目录中的Velocity模板或作为参数提供。用户定义的configset可以包含可呈现的、潜在的恶意模板。参数提供的模板在默认情…

ADS学习笔记 5. 微带天线设计

基于ADS2023 update2 参考书籍&#xff1a;卢益锋老师《ADS射频电路设计与仿真学习笔记》 更多笔记&#xff1a;ADS学习笔记 1. 功率放大器设计ADS学习笔记 2. 低噪声放大器设计ADS学习笔记 3. 功分器设计ADS学习笔记 4. 微带分支定向耦合器设计 目录 0、设计指标 1、微带…

【JAVA】使用IDEA创建maven聚合项目

【JAVA】使用IDEA创建maven聚合项目 1.效果图 2.创建父模块项目 2.1删除父模块下面的src目录以及不需要的maven依赖 3创建子模块项目 3.1右击父模块项目选择Module… 3.2创建子模块 3.3删除子模块下不需要的maven依赖 4.子模块创建完成后引入SpringBoot依赖启动项目

一文详细深入总结服务器选型

1. 题记&#xff1a; 服务器选型工作是项目规划检讨的一项非常重要的工作&#xff0c;本文详细深入总结服务器选型。 2. 服务器基础知识概览 2.1 服务器的定义与功能 2.1 .1 定义 服务器是一种高性能计算机&#xff0c;其设计目的是在网络中提供服务。它可以处理来自多个客…

如何编译 Cesium 源码

如何编译 Cesium 源码 Cesium 是一个开源的 JavaScript 库&#xff0c;用于构建 3D 地球和地图应用程序。它提供了一套强大的 API 和工具&#xff0c;使开发者能够创建丰富的地理空间应用。本文将指导您如何从 GitHub 下载 Cesium 源码&#xff0c;并在本地进行编译。 TilesB…

车载诊断架构 --- 关于DTC的开始检测条件

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所有人的看法和评价都是暂时的,只有自己的经历是伴随一生的,几乎所有的担忧和畏惧,都是来源于自己的想象,只有你真的去做了,才会发现有多快乐。…