与哈希函数有关的结构

一、哈希函数的特性

1.哈希函数的输入域是无穷大的,而输出域是相对有限的,但是也可以非常大。如:0~2的64次方-1,和 0 ~2的128次方-1 。

输入域可以理解为是一个字符串,而输出域可以理解为整数。

**因为输入域无穷大,输出域相对有限,那么某两个输入域经过哈希函数算出哈希值,有可能是相等的。**这种就称为是哈希碰撞。

2.哈希函数没有任何随机的成分,同一个字符串用哈希函数算出的哈希值一定是相同的。现实世界中,基本上传进来的东西都可以转换成字符串,如果传进来的是一个对象,那就拿它的内存地址做字符串

3.当有大量的输入时,经过哈希函数算出哈希值,肯定对应到某个输出域中。由于哈希函数有离散型(均匀性),哈希函数跟输入的数据状况是解耦的,即使是大量长得很像的不同输入,经过哈希函数的处理后,也能保证每一个输出不同,并且均匀分布。即使一点点的不同,也能被哈希函数放大得特别大。相关的算法可以了解md5的特性:定长、分散、不可逆。
在这里插入图片描述

二、哈希表的增删改查为什么是O(1)

经典的哈希表实现都是数组加链表,也有优化的实现,数组加红黑树。具体的可以了解哈希表的源码。

数组是哈希表一个初始的桶区域,数组长度就是桶的个数,假设17,存记录的时候,先根据哈希函数算出一个哈希值(hashcode),然后再模17,得到的结果肯定是在 0 ~ 16 之间,然后挂在相应的区域下面;

如果多个记录模完后值一样,那就顺着链表往下挂记录。删除,修改和查询操作也一样,根据哈希函数,相同的输入会导致相同的输出,将得到的哈希值模完17后,找到数组对应的位置,然后顺着链表往下操作。

如果一共N条记录的话,每一个桶就是N/17的长度,那么增删改查不是O(N)吗?因为链表的长度决定遍历时候的代价。此时就涉及到了哈希表的扩容操作。

如果链表的长度达到了一定长度(多大长度会认为操作比较慢呢?不知道,各个语言有自己的规定,我们这里假设是8),就在原数组的基础上扩充长度的一倍,之前是17,那么现在桶的个数就是34,然后之前的每一条记录都全部重新算哈希值,重新模上数组的长度,毫无疑问,扩容之后,链表的长度一定差不多是之前的一半。

假设哈希表最挫时候的样子,啥叫最挫的样子,就是链表长度到2的时候就很难受,就需要扩容,那么就是每个桶只能存一条记录。

所以,接下来,第2条记录需要扩容(哈希表大小变为4)、第5条记录需要扩容(哈希表大小变为8)、第9条记录需要扩容(哈希表大小为16)、第17条记录需要扩容(哈希表大小为32)、第33条记录需要扩容(哈希表大小为64)…总的来说是有logN次的扩容。

那么总的扩容代价就是 O(2) + O(3) + O(5) + O(9) + O(17) + … + O(N),也就是说第几条记录扩容的时候承担就是几个数的改动,是一个等比数列,所以总的代价就是O(N),即有多少个数产生变动的规模,均摊后就是单次代价,就是O(1)

既然最挫样子时候的代价都很不错了,那么初始数组长度不是1的时候就不用说了,因为扩容的次数也会变少。

所以,其余的优化都只是常数时间的优化,比如链表改用红黑树。

三、布隆过滤器

1)利用哈希函数的性质
2)每一条数据提取特征
3)加入描黑库

题:有一个大集合,里面有大量的黑名单URL,假设这个集合有100亿那么大,每个URL也不会超过64字节,把这个集合变成大服务。当输入一个URL的时候,就在集合中找该URL是否是黑名单里的,如果该URL在黑名单里,不显示;否则显示。

如果直接用哈希Set对10亿个URL进行存储,未免太浪费空间了,需要640G的空间来存储。

这里可以考虑使用布隆过滤器,但是布隆过滤器一定有失误率。这里的失误率是指:如果一个URL的确不是黑名单中的,则很有可能会误判是黑名单里的,导致不显示。而如果一个URL是黑名单里的,则肯定不会显示

各个公司一定会爬虫,去爬取不同的URL。当要爬取URL的时候,先去服务中找是否爬过URL中的数据,如果爬过就去爬下一个URL,能够避免爬取重复的URL而浪费空间。

讲到布隆过滤器就肯定离不开位图。位图是由多个比bit为单位的数组组成的。假设数组长度为m,则大小为m / 8 个B那么多。此时如果设置三个哈希函数f1,f2,f3。假设现在有一个String来了,它要经过f1得到哈希值后%m,再将算出的结果对应到数组中进行描黑。再经过f2同样地步骤进行描黑,再经过f3

如果此时要查一个String是否在黑名单中,就将该String经过上面过程,如果有一个没描黑,就说明该String不在黑名单中。

不过要注意一个问题:如果m很小,100亿个数据的时候,就把全部都抹黑了,再查哪个String哪个String就肯定是在黑名单里;如果m很大,就很浪费空间。因此不可否认,m越大失误率就越下降,但是失误率仍不可避免

总趋势:m越大失误率下降,m越小失误率上升。

思考:m跟什么有关系?1.样本量(100亿) 2.失误率(万分之一) 3.单样本大小(64字节),显然,跟条件三无关。

单样本的大小和设计布隆过滤器没有一点关系,不会决定布隆过滤设计的任何细节,例如上述例子中 “每个URL最大占64字节”。只要我设置的哈希函数可以接收64字节的URL作为参数,并能够对其进行计算就可以了。

下图表示的是失误率P、位图容量m,哈希函数个数K的关系图:

在这里插入图片描述
如果只使用1个哈希函数然后去描黑,那么会有采样不足的问题。有可能使得失误率变大。但如果用很多哈希函数作取样,那么位图将会被快速描黑,失误率也会上升(位图空间迅速耗尽)

计算公式:
在这里插入图片描述
如果面试被问到,当n为100亿,P为万分之一,求出m后转为B求出内存大小,大概是17G(理论值)。现在17G的空间就能够支持题目的要求,此时再问面试官能否再扩一下空间,如:升到20G,就能够把失误率降到更小。

如果面试官同意了,此时就有两个值,m为理论值17G,m‘ 为实际拿到的值

因此可以拿m’和n去计算实际的K是多少,大概是需要12.7个哈希函数,向上取整为13。
由下面的公式,能够求出真实的失误率是多少。
在这里插入图片描述
按照面试官允许多m进行扩增下,得到的失误率一定比用原本理论值m算出理论P更小

因此如果要设计一个服务或系统的时候,要问面试官允许有失误率?如果允许就用布隆过滤器解答,否则就另寻方法。

现在有个问题:如果需要13个哈希函数,那么这么多个哈希函数从哪找去
答:实际上只需要知道两个哈希函数就可以了,第三个哈希函数:1f1的返回值+f2,第四个哈希函数:2f1的返回值+f2,可以明确地说各个哈希函数都是几乎独立的。
如果设计出的哈希函数,超出了f1和f2的范围,但没有关系,最后都会进行%m对应到数组当中。

我计算了下,如果以100亿的数据,万分之一的失误率,到最后求出的布隆过滤器的大小为18G,相比直接用哈希Set记录(640G)省的不是一星半点…

四、一致性哈希

1.引入

一致性哈希是解决数据存储的问题

我们先说一个经典的服务器结构:有前端服务器,如nginx、tomcat等,有逻辑服务器。还有数据端服务器,如:Oracle、MySQL等。首先,前端的服务器中的数据会打到逻辑服务器中,再由哈希函数求出哈希值得到模上数据服务器的数量(假设3),就得到一个0~2的数字。如果是0,则将数据放入0号机器上。

由哈希函数的性质知:如果有很多个不同的key,会均分地到每一个数据服务器上。

HashKey是决定一个数据最后进行分片的一个关键,即HashKey由哈希函数得到哈希值后 %上机器数得到一个具体数,就决定放到哪台机器上。因此选择合适的HashKey是非常关键的。
如果将国家名的第一个字作为HashKey,假设有美、中,还有三台机器。即使按照国家名来划分,最后各个机器的请求数是均分的,但是由于美和中的请求数过多,就会导致某一台服务器的请求没有中美那么频繁。各台机器在业务上有高低频,就也会导致各个机器负载不均。
因此每台机器的请求应该都有高、中、低频的量,才会负载均衡。这个只能从业务上去挖掘,什么KEY才适合做HashKey。

就算各台机器负载均衡后,又出现了一个问题。当此时要加一台机器,即4台。此时就需要把所有的key%4放入到各个机器中保持负载均衡,数据迁移的代价是全量的,就很难受了,无论加机器还是减机器都很难受

一致性哈希就能够在加或减一台机器时,数据的迁移不是全量的,并且能够做到各个机器负载均衡。

2.原理

一致性哈希中是没有 “模” 这个操作的,那么具体是如何完成数据的均匀存储呢?

假设使用MD5算法作为哈希函数,计算出来的哈希值范围是 [ 0,(2^64) - 1 ],我们将整个哈希值域 [ 0,(2^64) - 1 ] 抽象成一个闭环

假设当前有三台数据服务器,分别为:0号、1号和2号数据服务器。
每一台数据服务器的机器信息(IP,Host name,MAC地址)肯定都不一样,只要能够选择一种机器信息能够将每一台数据服务器都区分开,该机器信息就可以作为每一台数据服务器的传入哈希函数的参数。例如使用MAC地址作为区分服务器的机器信息,那么哈希函数就会计算每一台数据服务器的MAC地址,最后得到不同的out,对应到闭环中。

如果现在需要存储 { “name”: “John”, “pass”: “123” } 该条数据,那么应用服务器会先将该条数据的Key(唯一标识) “John” 放入哈希函数中进行计算得到out,out一定能对应到 [ 0,(2^64) - 1 ] 中的某一个位置,然后将该条数据存储到对应位置顺时针距离最近的数据服务器上。如:
在这里插入图片描述
如果两台机器出现了哈希碰撞,放到了一起,该怎么解决?。假设此时有个数据在算出的哈希值比两台机器代表的哈希值小,大不了就将该数据归属于两台服务器当中,但是这种情况出现的概率极低。如:
在这里插入图片描述
一致性哈希有什么好处?假设此时要加一台服务器,即4台服务器。如下图哈希环所示,在没有添加新数据服务器时,0号数据服务器存储哈希值落在C区域的数据,1号数据服务器存储哈希值落在A区域的数据,2号数据服务器存储哈希值落在B区域的数据。当集群中添加一个新数据服务器3号,那么仅需要0号数据服务器将C区域的数据迁移到3号数据服务器上即可,大大降低了添加 / 减少数据服务器时数据迁移的代价。如:
当然,如果要减少一台服务器的时候,如减少3号服务器,只需要把C那段的数据归属到0号服务器当中即可,不需要大量迁移数据

那么数据找到自己的服务器归属的机制如何实现
在每个应用服务器上维护所有数据服务器通过MD5(MAC)计算得到的哈希值的一个有序排列,同时每个应用服务器还需要记录每一个哈希值对应哪一个数据服务器。上述例子的有序排列就是 [ ( 0 — 10亿),( 1 — 5000亿 ),( 2 — 70000亿 ) ]。
如果现在要通过name = "John"查询pass,那么在接收到该请求的应用服务器中,先将 “John” 放入哈希函数中计算得到4500亿,然后拿着计算出来的4500亿在该排序做二分查找,就能找到排序中大于4500亿且距离最近的一个哈希值为5000亿,因此应用服务器就会去哈希值为5000亿对应的1号数据服务器中检索该数据条目。
如果有数据服务器的哈希值正好等于4500亿,则应用服务器就直接去该数据服务器中检索数据条目,而不需要顺时针再去找数据服务器。
如果哈希函数计算出的哈希值在排序中没有找到大于等于该值的,那么对应的就是有序排列中最小的哈希值对应的那个数据服务器(因为是一个闭环)。

但是,此时还存在两个潜在问题:
1.在数据服务器数量很少时,一开始可能做不到数据的均匀存储。(因为哈希函数的特征是当数据多起来时落在每一段的数据数量差不多,而不是只有三个数据的时候三个数据必须分别落在三个段上)
2.即便在数据服务器很少的情况下可以把哈希环均分,当增加 / 减少一台数据服务器时,马上负载不均衡。(比方说本来三台数据服务器各占哈希环的 1 / 3,当加入一台新的数据服务器时,其中两台还占 1 / 3,但是另外两台却各自占 1 / 6)

这两个潜在问题可以使用一个技术来解决,虚拟节点技术

3.虚拟结点技术

假设在上述例子的基础上,给0号数据服务器分配1000个虚拟节点(a1,a2,…,a1000),给1号数据服务器也分配1000个虚拟节点(b1,b2,…,b1000),给2号数据服务器也分配1000个虚拟节点(c1,c2,…,c1000)。
不是0号、1号和2号数据服务器去争抢哈希环了,而是这些数据服务器下的虚拟节点去抢环。每个虚拟节点中存储一个代表字符串,通过将字符串传入哈希函数,计算得到的哈希值落在哈希环的对应位置上

当前,整个哈希环被3000个虚拟节点所争抢。其中,哈希环中有1000个落点属于0号数据服务器,1000个落点属于1号数据服务器,1000个落点属于2号数据服务器。由于哈希函数的特性,这3000个落点在每一段位置中都分布均衡。因此随便取一段,a、b和c虚拟节点的个数都是差不多的,基本上保持1 / 3属于0号数据服务器,1 / 3属于1号数据服务器,1 / 3属于2号数据服务器

按照比例去争抢数据,就能解决初始数据存储不均和增加 / 减少数据服务器导致数据存储不均的问题。
当增加一台数据服务器时,那么就为该数据服务器再创建1000个虚拟节点,再将1000个落点打到哈希环中。那么此时哈希环中每台数据服务器都各占1 / 4的落点,此时新数据服务器一定会从0号、1号和2号数据服务器夺取等量的数据到自身中。
当减少一台数据服务器时,该数据服务器也会将自身数据等量的分给其他三台数据服务器,给完之后其他三台数据服务器的数据也能够保持均衡。

写一个简单的底层逻辑就可以实现虚拟节点之间的数据迁移。例如将a10的数据迁移到b500,通过路由表,a10去0号数据服务器上找数据,然后传输到b500虚拟节点对应的1号数据服务器中。

4.管理负载

如果按照比例去争抢数据,那么一致性哈希还有一个妙用,就是可以通过实际机器的状况来管理负载。
比如在上面的例子中,0号数据服务器的性能比1号、2号数据服务器的性能都强。那么我们可以给0号数据服务器分配2000个虚拟节点,给1号、2号数据服务器各分配500个虚拟节点。

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zjruiiiiii

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

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

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

打赏作者

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

抵扣说明:

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

余额充值