赞
踩
哈希函数(散列函数):类似于函数调用一样,给一个字符串作为输入,返回一个哈希码。
哈希表(散列表):把哈希码映射到表中的一个位置来访问记录,存放记录的数组就叫散列表
性质:
1)输入域是无穷大的
2)输出域是有穷的,输出域要比输入域小
3)哈希函数不是一个随机函数,只要输入一样,输出就一样
4)输入域这么大而输出域这么小,而由于特性3的关系,就会导致输入不一样也有可能得到输出一样的值(哈希碰撞)
5)虽然有哈希碰撞,如果输入够多,将在整个输出域上均匀地出现返回值,不一定完全平分,但输出域中的每个哈希码一定被差不多个输入域中的值对应
6)因为要做到均匀分布,于是哈希函数跟输入的规律是没有关系的,可以打乱输入规律
推广:
当输入域足够大,经过一个哈希函数计算完后保证输出域是均匀分布时,
对输出域的每一个哈希码模%上一个m,最终的域会缩减成0至m-1,也均匀分布
工程应用:
如何找到1k个相对独立的哈希函数,一个哈希函数的规律跟另一个哈希函数的规律是无关的
只要用1个哈希函数就能改出1k个来,如:哈希f1 + k * 哈希f2 = 哈希fn
实现原理:
解决哈希冲突(key1和key2称为同义词)的方法:
装载因子
线性增长。哈希表扩容:
如果发现某个位置上的链的长度到达k了,那么可以认为其它位置上链的长度也到达k了。此时,可能下一次加记录的话效率就不行了,这时就要经历哈希表的扩容
- 首先要将原来的哈希域(如前面的17),扩展到104(或别的)。
- 扩完之后如果要模104,则原来的记录都会失效。此时要将每一个记录都拿出来,用哈希函数计算完之后去模上104,再分配其在哪个位置。
那么,扩容代价不用计算吗?为什么能做到O(1)呢?
一方面样本量是N,若每次扩容都让哈希域长度增加k倍,则扩容平均代价是O(logkN)可很低
另一方面还可以离线扩容。
于是哈希表的增删改查是O(1)的。
Q:
有一个大文件100T,每一行是一个字符串,是无序的,让你打印重复的字符串
A:
如果单核单CPU单台机器去搞这个事情,那头都大了
Q:
说得对,那你怎么办?
A:
这是一个大数据问题,可以通过哈希来分流,你给我多少台机器呢?大文件存在哪呢?
Q:
给你1000台机器干这个事情,大文件存在一个分布式文件系统上,按行读有很快的工具
A:
- 1)首先对机器标号0-999,把每一行作为一个文本读出来,
利用哈希函数去算一个哈希码,去模1000,
当然也不一定要你提供的1k台机器,只要我能知道里面有m种字符串就好了,就模m- 2)然后将结果放到对应标号的机器中,用一个文件记录下来。
这样就把这个大文件分到了m台机器上去,而且相同的重复文本一定会分配到同一台机器上(哈希函数的性质:相同输入相同输出,不同输入均匀分布)- 3)然后在单台机器上去统计有哪些重复的,最后就是1k台机器并发
如果单台机器上的文件还是太大,再在这个机器上做相同的步骤,在单台机器上并行计算即可
主要用于解决爬虫去重问题以及黑名单问题,其短板是它有失误率。
这个失误是指:宁可错杀,绝不放过1个的类型。如果是坏人,一定报警。如果是好人,也可能报警,但几率较小
以黑名单问题来举例:
有这么个需求,100亿个URL是黑名单,假设每个URL是64字节,
当用户想搜索某个URL时,如果它是黑名单里的URL,就要对它进行过滤
解决方案:
1)首先它可以通过哈希表实现,但是需要多少个空间呢?
2)那怎么能仅适用一台机器就能提供服务呢?——布隆过滤器
实现方法:
第一步:
准备一个数组,从0到m-1位置,每个位置上不是整数也不是串,是一个bit,只有两种状态0/1
int *arr = new int[1000]; // 分配4*1000字节的连续内存,共4*1000*8个bit
这样,就用基础类型int做出了一个0到m-1长度为m的bit类型的数组。如果想省空间,就用long类型,8个字节64位。此外还能做成矩阵的形式。
如果想让第300000个bit位置置1,此时int index = 300000,
- ① 定位这个bit来自于哪个桶
int intIndex = index / 32;
- ② 定位这个位置来自于这个桶的哪个bit位
int bitIndex = index % 32;
- ③
arr[intIndex] = (arr[intIndex] | (1 << bitIndex));
第二步:
有了这个数组以后,
- ① 将黑名单内的所有URL,
输入k个相互独立的哈希函数中
,得到k个哈希码
,分别取模上m
,算出来的对应数组中的那个位置1。若产生哈希碰撞,不管,还是置1。这个过程结束以后,URL进入布隆过滤器。注意:这个数组应当长一些,如果太短,则很容易每个位置都变成1,这样用户输入的URL就全都是黑名单了。
- ② 查URL的过程:将用户提供的URL,也做步骤①,如果算出来的k个位置都已经置1,则说这个URL在黑名单中。如果仅有一个甚至几个没被置1,说明这个URL不在黑名单中(
哈希函数特性:相同输入一定有相同输出
)。失误的时候就出现在数组开的比较小。- 通过调整数组的大小m和哈希函数的个数k,可以得到不同的失误率。
经典服务器抗压结构:
比如提供一个根据用户QQ号(key)返回其网龄(value)的服务。如何使其负载均衡呢?
- 首先会有一个前端(Nginx),用于接受请求,请求可以发送到任意一个前端上,提供的服务相同
- 然后有一个后端集群组,假设有3台机器m1,m2,m3
- 首先信息会发送到前端上,经前端的同样一份哈希函数算出哈希码,模上3得到对应的机器,将这个信息存到对应的机器中。如果数据足够多,数据是均匀地存在三台机器中的,
负载均衡
:每台机器处理的东西是差不多的,CPU占用差不多,内存使用差不多,相关系统性能指标差不多。- 在查的时候,依然是经过上面的步骤,由于相同输入一定导致相同输出,就会到相应的机器上去拿出这个记录,返回给前端,然后前端返回给用户。
上面提到的经典服务器有什么问题呢?——当增加机器或减少机器时,它就炸了
原因:
这和哈希表扩容是一样的,如果加机器加到100,原来模的是3,现在要模100,所有的数据归属全变了。所以必须要把原来的记录都拿出来再算一次,然后迁移到相应的机器上。代价很高
于是有了一致性哈希结构
,可实现把原来全部数据迁移的代价变得很低,并负载均衡
。
具体是这样的:
那么如何实现它呢?
- 对于后端——还是那三台机器
- 对于前端——还是无差别的负载的服务器
然后,
对三台机器对应的哈希值排好序之后做成一个数组
,存到前端中去
。
这样,不管请求发送到前端的哪一个机器上,由于每个机器上都有这么一个数组,二分的方式
找到第一个大于等于算出来的哈希值
的那个位置,就知道这个请求应当由哪个机器处理了。
即便你有10亿台机器,由于采用二分的方法,log以后复杂度也是很低的。
那为什么说它克服了传统服务器结构中加减机器的问题呢?
比如加了一台m4,同
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。