海量数据去重的Hash和BloomFilter

2022/3/2 6:15:36

本文主要是介绍海量数据去重的Hash和BloomFilter,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、背景

  • 使用 word 文档时,word 如何判断某个单词是否拼写正确?
  • 网络爬虫程序,怎么让它不去爬相同的 url 页面?
  • 垃圾邮件过滤算法如何设计?
  • 公安办案时,如何判断某嫌疑人是否在网逃名单中?
  • 缓存穿透问题如何解决?

需求

上面的需求都是从海量数据中查询某个字符串是否存在?

二、平衡二叉树

平衡二叉树的增删查改的时间复杂度为O(log2n);

平衡的目的是增删改后,保证下次搜索能文帝排除一半的数据;

O(log2n)的直观理解:100万个节点,最多比较 20 次;10亿个节点,最多比较 30 次;

总结:通过比较保证有序,通过每次排除一半的元素达到快速索引的目的;

三、散列表

散列表,使用hash函数,根据key计算key在存储表中的位置的数据结构;hash函数是key和其所在存储地址的映射关系。

hash函数

映射函数 Hash(key)=addr ;

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

选择hash

  • 计算速度快(尽量将乘除法和取模运算转换成位运算)
  • 强随机分布(等概率、均匀地分布在整个地址空间)
  • murmurhash2,siphash(redis6.0当中使用,rust等大多数语言选用的hash算法来实现hashmap),cityhash都具备强随机分布性;测试地址如下:https://github.com/aappleby/smhasher

siphash主要解决字符串接近的强随机分布性,当key比较接近时,哈希经过也会比较接近,它的随机分布性就会出现问题。

负载因子

数组存储元素的个数/数据长度;用来形容散列表的存储密度;负载因子越小,冲突越小;负载因子越大,冲突越大。

冲突处理

  • 链表法
    • 引用链表来处理哈希冲突;也就是将冲突元素用链表链接起来(见附1);这也是常用的处理冲突的方式;但是可能出现一种极端情况,冲突元素比较多,该冲突链表过长,这个时候可以将这个 链表转换为红黑树;由原来链表时间复杂度 转换为红黑树时间复杂度 ;那 么判断该链表过长的依据是多少?可以采⽤超过 256(经验值)个节点的时候将链表结构转换为红黑树结构;
  • 开放寻址法
    • 将所有的元素都存放在哈希表的数组中,不使用额外的数据结构;一般使用线性探查的思路解决;
    1. 当插入新元素的时,使用哈希函数在哈希表中定位元素位置;
    2. 检查数组中该槽位索引是否存在元素。如果该槽位为空,则插⼊,否则3;
    3. 在 2 检测的槽位索引上加一定步长接着检查2; 加⼀定步长分为以下几种:
      1. i+1,i+2,i+3,i+4, ... ,i+n
      2. i- ,i+ ,i- ,1+ , ...
    4. 这两种都会导致同类 hash 聚集;也就是近似值它的hash 值也近似,那么它的数组槽位也靠近,形成 hash 聚集;第一种同类聚集冲突在 前,第二种只是将聚集冲突延后; 另外还可以使用双重哈希来解决上面出现hash 聚集现象
      1. 在.net HashTable类的hash函数Hk定义如下:
      2. Hk(key) = [GetHash(key) + k * (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1)))] % hashsize
      3. 在此 (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1))) 与 hashsize 互为素数
      4. 两数互为素数表示两者没有共同的质因⼦;
      5. 执⾏了 hashsize 次探查后,哈希表中的每⼀个位置都有且只有⼀次被访问到, 也就是 说,对于给定的 key,对哈希表中的同⼀位置不会同时使⽤ Hi 和 Hj
      6.  具体原理:https://www.cnblogs.com/organic/p/6283476.htm

四、布隆过滤器

背景

布隆过滤器是一种概率型数据结构,它的特点是高效地插入和查询,能确定某个字符串一定不存在或者可能存在

布隆过滤器不存储具体数据,所以占用空间小,查询结果存在误差,但是误差可控,同时不支持删 除操作(见附2);

构成

位图(Bit数组)+n个hash函数

 

原理

当一个元素加入位图时,通过 k 个 hash 函数将这个元素映射到位图的 k 个点,并把它们置为 1; 当检索时,再通过 k 个 hash 函数运算检测位图的 k 个点是否都为 1;如果有不为 1 的点,那么认 为该 key 不存在;如果全部为 1,则可能存在;

1. 为什么不支持删除操作?

  • 在位图中每个槽位只有两种状态(0 或者 1),一个槽位被设置为 1 状态,但不确定它被设 置了多少次;也就是不知道被多少个 key 哈希映射而来以及是被具体哪个 hash 函数映射而来;

 

 应用场景

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

常见处理场景:① 缓存穿透的解决;② 热 key 限流;

 

  •  (缓存场景)为了减轻数据库(mysql)的访问压力,在 server 端与数据库(mysql)之间加入 缓存用来存储热点数据;
  • (数据请求步骤)如上图右上边 所示;
  • (缓存穿透)server端请求数据时,缓存和数据库都不包含该数据,最终请求压力全部涌向数据 库;
  • (发生原因)黑客利用漏洞伪造数据攻击或者内部业务 bug 造成大量重复请求不存在的数据;
  • (解决方案)如上图右下边所示;

应用分析

在实际应用中,该选择多少个 hash 函数?要分配多少空间的位图?预期存储多少元素?如何控制 误差? 公式如下:

假阳性指误差率,如0.01可以看作100个数据允许出现1个失败。

布隆过滤器的使用(见附3)

  • 一般先确定n和p,然后根据上面的公式计算出m和k;可以借助这个网站计算:https://hur.st/bloomfilter
  • 计算出k之后,就要选择k个哈希函数,一般选择一个 hash 函数,通过给 hash 传递不同的种子偏移值,采用线性探寻的方式构造多个 hash 函数;
    1 #define MIX_UINT64(v) ((uint32_t)((v>>32)^(v)))
    2 uint64_t hash1 = MurmurHash2_x64(key, len, Seed);
    3 uint64_t hash2 = MurmurHash2_x64(key, len, MIX_UINT64(hash1));
    4 for (i = 0; i < k; i++) // k 是hash函数的个数
    5 {
    6 Pos[i] = (hash1 + i*hash2) % m; // m 是位图的⼤⼩
    7 }

五、分布式一致性hash

分布式一致性hash可以用来解决分布式缓存的负载均衡(见附4)问题。

背景

在分布式环境下,我们会设置n服务器节点,我们使用hash(ip)%n得到服务器的编号,在插入数据时,我们使用hash(key)%n得到该数据要存储的服务器。此时,如果我们增加或删除服务器节点,则n发生改变,从而导致原有的服务器编号不适用。分布式一致性hash算法就是解决这个问题。

分布式一致性 hash 算法将哈希空间组织成一个虚拟的圆环,圆环的大小是232

算法为: hash(ip) ,最终会得到一个 [0, 232] 之间的一个无符号整型,这个整数代表 服务器的编号;多个服务器都通过这种方式在 hash 环上映射一个点来标识该服务器的位置;当用 户操作某个 key,通过同样的算法生成一个值,沿环顺时针定位某个服务器,那么该 key 就在该服务器中。

 

数据迁移

分布式一致性hash有效避免数据的全量迁移,需要迁移的只是更改的节点和它的上游节点它们两个节点之间的数据。

hash偏移

hash 算法得到的结果是随机的,不能保证服务器节点均匀分布在哈希环上;分布不均匀造成请求 访问不均匀,服务器承受的压力不均匀;

 

 

 虚拟节点

为了解决哈希偏移的问题,增加了虚拟节点的概念;理论上,哈希环上节点数越多,数据分布越均衡(hash的强随机分布性)。

为此,我们为每个服务器计算多个哈希点(虚拟节点),然后再插入哈希空间;

通常的做法是:hash("IP:PORT:seqno")%232。(seqno为自定义的偏移量)

 

 

-------------------------------------------------------------------------------------

附录

附1

散列表每个存储位置都可以转换成链表。而链表数据的插入分头插法和尾插法。数据库常使用头插法,因为数据库认为最近插入的元素,未来也是最先访问到的。

附2

布隆过滤器的不可删除是由于储存方式导致的,如果确实需要删除操作,可以使用两个布隆过滤器,第二个布隆过滤器存储删除的元素

附3

k我们常常会选择31,因为

  1.  i * 31 可以很方便的转换成位运算 i * (32-1) = i << 5 -1
  2.  比较常用的[17,31,101]中质数31的hash随机分布性是最好的

附4

常见的负载均衡方法有很多,但是它们的优缺点也都很明显:

  • 随机访问策略。系统随机访问,缺点:可能造成服务器负载压力不均衡,俗话讲就是撑的撑死,饿的饿死。
  • 轮询策略。请求均匀分配,如果服务器有性能差异,则无法实现性能好的服务器能够多承担一部分。
  • 权重轮询策略。权值需要静态配置,无法自动调节,不适合对长连接和命中率有要求的场景。
  • Hash取模策略。不稳定,如果列表中某台服务器宕机,则会导致路由算法产生变化,由此导致命中率的急剧下降。
  • 一致性哈希策略


这篇关于海量数据去重的Hash和BloomFilter的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程