开发面试Hash常见算法

Hash常见算法

  • 1. 一致性Hash算法
    • 使用场景
    • 一致性hash算法要求
    • Hash环形空间
    • 机器删除与添加
    • 平衡性分析
    • 总结
  • 2. SimHash算法
    • SimHash算法思路
    • 海明距离
    • SimHash应用
  • GeoHash函数
    • 3. GeoHash使用示例
    • GeoHash优缺点

Hash部分分为三部分讲解,各位游客可根据分类进行对应博客阅读:

  1. 开发面试Hash原理详解
  2. 开发面试Hash常见算法
  3. 开发面试Hash面试考题

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

本部分主要讲解几种在实际开发中常被运用的几种Hash算法。

1. 一致性Hash算法

一致性Hash算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot Spot)问题,初衷和CARP十分相似。一致性Hash修正了CARP使用的简单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用。

Consistent Hashing 一致性hash的原理简单的来说,就是在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在key 映射关系,尽可能的满足单调性的要求。

使用场景

比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object 映射到 N 个 cache 上呢,你很可能会采用类似下面的通用H(key)=Key%N的方法计算 object 的 hash 值,然后均匀的映射到到 N 个 cache 。

使用简单取余hash算法不能解决分布式问题,如果大量用户数据在存储分布式中使用Hash(Key)=object%N,N指N个cache服务器或者节点,用这种hash算法是不满足分布式要求的。我们如下分析:

  1. 如果N个cache服务器中编号为a的服务器故障了,需要把a从服务器群中移除,这个时候cache服务器的数量就变成了N-1台,那么所有对象(object)映射到cache服务器的计算公式就变成了hash(object)%N-1,对,影响到了所有的对象与cache服务器的映射关系。

  2. 类似,由于访问加重,需要添加cache服务器,这时候cache服务器是N+1台,映射公式就变成了hash(object)%N+1,这就意味着几乎所有的cache都失效了。

  3. 由于硬件能力越来越强,你可能想让后面添加的节点多做点活,但是用Hash(Key)=object%N算法无法做到有效分配任务

如1、2两种情况意味着突然之间几乎所有的 cache 都失效了。对于服务器而言这是一场灾难,洪水般的访问都会直接冲向后台服务器。

在分布式集群中,对机器的添加删除,或者机器故障后自动脱落集群这些操作是分布式集群管理最基本的功能。如果采用常用的hash(object)%N算法,那么在有机器添加或者删除后,很多原有的数据就无法找到了,这样严重的违反了单调性原则,所以就出现了一致性hash算法来解决分布式中碰到的问题。

一致性hash算法要求

在动态变化的Cache环境中,良好的一致性hash算法应该满足以下几个方面
1.平衡性(Balance)
平衡性是指哈希的结果能够尽可能分布在所有的缓冲(Cache)中去,这样可以使得所有的缓冲空间得到利用。很多哈希算法都能够满足这一条件。

2.单调性(Monotonicity)
单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应该能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会映射到旧的缓冲集合中的其他缓冲区。

简单的哈希算法往往不能满足单调性的要求,如最简单的线性哈希:x = (ax + b) mod §,在上式中,P表示全部缓冲的大小。不难看出,当缓冲大小发生变化时(从P1到P2),原来所有的哈希结果均会发生变化,从而不满足单调性的要求。哈希结果的变化意味着当缓冲空间发生变化时,所有的映射关系需要在系统内全部更新。

而在P2P系统内,缓冲的变化等价于Peer加入或退出系统,这一情况在P2P系统中会频繁发生,因此会带来极大计算和传输负荷。单调性就是要求哈希算法能够应对这种情况。

3.分散性(Spread)
在分布式环境中,终端有可能看不到所有的缓冲,而只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上去,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。

分散性的定义就是上述情况发生的严重程度。好的哈希算法应该能够尽量避免不一致的情况发生,也就是尽量降低分散性。

4.负载(Load)
负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射到不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

5.平滑性(Smoothness)
平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的。

Hash环形空间

按照常用的hash算法来将对应的key哈希到一个具有232次方个桶的空间中,即0~(232)-1的数字空间。现在我们可以将这些数字头尾相连,想象成一个闭合的环形。整个空间按顺时针方向组织,0和2^32-1在零点中方向重合。如下图:
开发面试Hash常见算法_第1张图片

1.现在我们把数据(对象)通过一定的hash算法处理后映射到环上
假设有object1、object2、object3、object4四个对象通过特定的Hash函数计算出对应的key值,然后散列到hash换上。如下图:
Hash(object1)=key1;
Hash(object2)=key2;
Hash(object3)=key3;
Hash(object4)=key4;
开发面试Hash常见算法_第2张图片

2. 将机器通过hash算法映射到环上
在采用一致性哈希算法的分布式集群中将新的机器加入,其原理是通过使用与对象存储一样的Hash算法将机器也映射到Hash环中。(一般情况下对机器的hash计算是采用机器的IP或者唯一的别名作为输入值)。

在这个环形空间中,如果沿着顺时针方向从对象的 key 值出发,直到遇见一个 cache ,那么就将该对象存储在这个 cache 上,因为对象和 cache 的 hash 值是固定的,因此这个 cache 必然是唯一和确定的。这样就将所有对象存储到了离自己最近的机器中。

假设现在有NODE1,NODE2,NODE3三台机器中,通过hash算法得到对应的KEY值,映射到环中,其示意图如下:
Hash(NODE1)=KEY1;
Hash(NODE2)=KEY2;
Hash(NODE3)=KEY3;
开发面试Hash常见算法_第3张图片
通过上图可以看出对象与机器处于同一个哈希空间中,这样按顺时针转动object1(对象)存储到了NODE1(机器)中,object3(对象)存储到了NODE2(机器)中,object2、object4(对象)存储到了NODE3(机器)中。

在这样的部署环境中,hash环是不会变更的,因此,通过算出对象的hash值就能快速的定位到对应的机器中,这样就能找到对象真正的存储位置了。

机器删除与添加

普通hash求余算法最为不妥的地方就是在有机器的添加与删除以后会造成大量的对象存储位置的失效,这样就大大的不满足单调性了。下面来分析一下一致性哈希算法是如何处理的。

1.节点(机器)的删除
以上面的分布式集群为例,如果NODE2出现故障被删除了,那么按照顺时针迁移的方法,object3将会被迁移到NODE3中,这样仅仅是object3的映射位置发生了变化,其他的对象没有任何的变动,如下图:
开发面试Hash常见算法_第4张图片

  1. 节点(机器)的添加
    如果往集群中添加一个新的节点NODE4,通过对应的Hash算法得到KEY4,并映射到环中,如下图:
    开发面试Hash常见算法_第5张图片
    通过按照顺时针迁移的规则,那么object2被迁移到NODE4中,其他对象还保持这原有的存储位置。通过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说非常合适的,避免了大量收数据迁移,减少了服务器的压力。

平衡性分析

根据上面的图解分析,一致性哈希算法满足了单调性和负载均衡的特性以及一般hash算法的分散性,但这还并不能当做其被广泛应用的原由,因为缺少了平衡性。

下面将分析一致性哈希算法是如何满足平衡性的。hash算法是不保证平衡性的,如上面只部署了NODE1和NODE3的情况(NODE2被删除的图),object1存储在NODE1中,而object2、object3、object4都存储在NODE3中,这样就造成了非常不平衡的状态。在一致性哈希算法中,为了尽可能的满足平衡性,其引入了虚拟节点。

虚拟节点
虚拟节点(Virtual node)是实际节点(机器)在hash空间的复制品(replica),一个实际节点对应了若干个“虚拟节点”,这个对应个数也称为“复制个数”,“虚拟节点”在hash空间中以hash值排列。

在上面只部署了NODE1和NODE3的情况(NODE2被删除的图)为例,之前的对象在机器上的分布很不均衡,现在我们以2个副本(每个节点复制2个)为例,这样整个hash环就存在4个虚拟节点,最后对象映射的关系图如下:
开发面试Hash常见算法_第6张图片

根据上图可知对象的映射关系:object1->NODE1-1,object2->NODE1-2 ,object3->NODE3-2,object4->NODE3-1,通过虚拟节点的引入,对象的分布就比较均衡了。

那么在实际操作中,真正的对象查询是如何工作的呢?对象从hash到虚拟节点到实际节点的转换如下图:
开发面试Hash常见算法_第7张图片

虚拟节点”的hash计算可以采用对应节点的IP地址加数字后缀的方式。例如假设NODE1的IP地址为192.168.1.100。引入“虚拟节点”前,计算 cache A 的 hash 值:

Hash(“192.168.1.100”);
引入“虚拟节点”后,计算“虚拟节”点NODE1-1和NODE1-2的hash值:
Hash(“192.168.1.100#1”); // NODE1-1
Hash(“192.168.1.100#2”); // NODE1-2

虚拟节点都对应着实际节点,可以自定义设计怎么分配。

总结

  1. 一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
  2. 一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。

2. SimHash算法

开发面试Hash常见算法_第8张图片
在比较网页或信息文本相似度时,理想的hash函数需要对几乎相同的输入内容,产生相同或者相近的hash值。换言之,hash值的相似程度要能直接反映输入内容的相似程度,故md5等传统hash方法也无法满足我们的需求。

simhash作为locality sensitive hash(局部敏感哈希)的一种,是google用于海量文本去重的一种方法。它将一篇文本最后转换成一个64位的字节,暂且称之为特征字,两篇文本是否相同比较两者的海明距离即可做出判断。

其主要思想是降维,将高维的特征向量映射成低维的特征向量,通过两个向量的Hamming Distance来确定文章是否重复或者高度近似。

局部敏感
假定两个字符串具有一定的相似性,在hash之后,仍然能保持这种相似性,就称之为局部敏感hash。

SimHash算法思路

simhash算法分为5个步骤:分词、hash、加权、合并、降维,具体过程如下所述:
1. 分词
给定一段语句进行分词,得到有效的特征向量,然后为每一个特征向量设置1-5等5个级别的权重(如果是给定一个文本,那么特征向量可以是文本中的词,其权重可以是这个词出现的次数)。

例如给定一段语句:“CSDN博客结构之法算法之道的作者July”,分词后为:“CSDN&博客&结构&之&法&算法&之&道&的&作者&July”,然后为每个特征向量赋予权值:CSDN(4) 博客(5) 结构(3) 之(1) 法(2) 算法(3) 之(1) 道(2) 的(1) 作者(5) July(5)。
其中括号里的数字代表这个单词在整条语句中的重要程度,数字越大代表越重要。

2. hash
通过hash函数计算各个特征向量的hash值,hash值为二进制数01组成的n-bit签名。
比如“CSDN”的hash值Hash(CSDN)为100101,“博客”的hash值Hash(博客)为“101011”。就这样,字符串就变成了一系列数字。

这里计算特征词的Hash算法自己选定,但是生成的长度一般一样长,也有的是直接64位长度,刚好被long型存储。

3. 加权
在hash值的基础上,给所有特征向量进行加权,即W = Hash * weight,且遇到1则hash值和权值正相乘,遇到0则hash值和权值负相乘。

例如给“CSDN”的hash值“100101”加权得到:W(CSDN) = 100101 4 = 4 -4 -4 4 -4 4,给“博客”的hash值“101011”加权得到:W(博客)=101011 5 = 5 -5 5 -5 5 5,其余特征向量类似此般操作。

4. 合并
将上述各个特征向量的加权结果累加,变成只有一个序列串。拿前两个特征向量举例,例如“CSDN”的“4 -4 -4 4 -4 4”和“博客”的“5 -5 5 -5 5 5”进行累加,得到“4+5 -4±5 -4+5 4±5 -4+5 4+5”,得到“9 -9 1 -1 1 9”。

5. 降维
对于n-bit签名的累加结果,如果大于0则置1,否则置0,从而得到该语句的simhash值,最后我们便可以根据不同语句simhash的海明距离来判断它们的相似度。

例如把上面计算出来的“9 -9 1 -1 1 9”降维(某位大于0记为1,小于0记为0),得到的01串为:“1 0 1 0 1 1”,从而形成它们的simhash签名。

海明距离

Hamming Distance,又称汉明距离,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。也就是说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:1011101 与 1001001 之间的汉明距离是 2。

我们常说的字符串编辑距离则是一般形式的汉明距离。如此,通过比较多个文档的simHash值的海明距离,可以获取它们的相似度。

海明距离计算

  1. 假设获取两段文本A的Simhash(A)=100111和B的Simhash(B)=101010
  2. 两者的海明距离为hamming_distance(A, B) = count_1(A xor B) = count_1(001101) =

A和B的海明距离是否小于等于n,这个n值根据经验一般取值为3,小于等于3说明文本相似性很高

大规模数据海明距离计算
在大规模数据量的情况下,如果对两个文本64位的SimHash的海明距离采用每一位比较的方法进行计算,找出海明距离小于等于3的文本,这样会耗费大量时间和资源。

那么如何在海量的样本库中查询与其海明距离在3以内的记录呢?

  1. 一种方案是查找待查询文本的64位simhash code的所有3位以内变化的组合
  2. 另一种方案是预生成库中所有样本simhash code的3位变化以内的组合
    这两种方案,要么时间复杂度高,要么空间复杂度复杂,能否有一种方案可以达到时空复杂度的绝佳平衡呢?

一种较好的来均衡计算海明距离的时间复杂度和空间复杂度的算法思路是:

  1. 把64位的SimHash分成四个part,如果两个SimHash相似(海明距离小于等于3),根据鸽巢原理,必然有一个part是完全相同的。
  2. 如果已存在一个对应part相同,则再进行part中的海明距离计算

SimHash应用

每篇文档得到SimHash签名值后,接着计算两个签名的海明距离即可。
根据经验值,对64位的SimHash值,海明距离在3以内的可认为相似度比较高。

如举例比较多个文档中的内容

  1. 将Doc进行关键词抽取(其中包括分词和计算权重),抽取出n个(关键词,权重)对, 即图中的多个(feature, weight)。记为 feature_weight_pairs = [fw1, fw2 … fwn],其中 fwn = (feature_n,weight_n)。

  2. 对每个feature_weight_pairs中的feature进行hash。然后对hash_weight_pairs进行位的纵向累加,如果该位是1,则+weight,如果是0,则-weight,最后生成bits_count个数字,大于0标记1,小于0标记0

  3. 最后转换成一个64位的字节,判断重复只需要判断他们的特征字的距离是不是 开发面试Hash常见算法_第9张图片

    两个文本只有一个字变化时,如果使用普通Hash则会导致两次的结果发生较大改变,而SimHash的局部敏感特性,会导致只有部分数据发生变化。
    开发面试Hash常见算法_第10张图片

GeoHash函数

geohash是由Gustavo Niemeyer发明的一套空间地理信息编码系统,能够将一个地理位置的经纬度信息转化为一串较短的数字和字母组成的字符串。geohash是一个层级空间数据结构,通过使用“Z型曲线”能够将空间划分到网格状的桶(buckets)中,一般也称为”空间填充曲线“。

可以理解geohash是一种算法思想,geohash就是把二维的坐标点,用一串字符串表示,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。通过比较geohash值得相似程度来查找附近目标要素。

geohash基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块,每个子块在一定经纬度范围内拥有相同的编码,这种方式简单粗暴,可以满足对小规模的数据进行经纬度的检索。

3. GeoHash使用示例

地球纬度区间是[-90,90],上海大宁国际广场坐标(121.458797,31.280291)。

1. 编码维度
大宁国际广场的纬度是31.280291,可以通过下面算法对纬度31.280291进行逼近编码:

  • 1)区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.928167属于右区间[0,90],给标记为1;
  • 2)接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定31.280291属于左区间 [0,45),给标记为0;
  • 3)递归上述过程31.280291总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近31.280291;
  • 4)如果给定的纬度x(31.280291)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的进行会产生一个序列1010110001,序列的长度跟给定的区间划分次数有关。 | Column 1
bit min mid max
1 -90.000 0.000
0 0.000 45.000 90.000
1 0.000 22.500 45.000
0 22.500 33.750 45.000
1 22.500 28.125 33.750
1 28.125 30.9375 33.750
0 30.9375 32.34375 33.750
0 30.9375 31.640625 32.34375
0 30.9375 31.2890625 31.640625
1 30.9375 31.1090625 31.2890625

2.编码精度
同理,地球经度区间是[-180,180],可以对经度121.458797进行编码。

bit min mid max
1 -180 0 180
1 0 90 180
0 90 135 180
1 90 112.5 135
0 112.5 123.75 135
1 112.5 118.125 123.75
1 118.125 120.9375 123.75
0 120.9375 122.34375 123.75
0 120.9375 121.640625 122.34375
1 120.9375 121.2890625 121.640625

3.编码组合
通过上述计算,纬度产生的编码为10101 10001,经度产生的编码为11010 11001。偶数位放经度,奇数位放纬度(从右往左),把2串编码组合生成新串:11100 11001 11100 00011。

最后使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将11100 11001 11100 00011转成十进制,对应着28、25、28、3,十进制对应的编码就是wtw3。

Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Base32 0 1 2 3 4 5 6 7 8 9 b c d e f g
Decimal 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Base32 h j k m n p q r s t u v w x y z

同理,将编码转换成经纬度的解码算法与之相反,对于附近的点即与wtw3进行比较,相近则内容会相近。

GeoHash优缺点

优点:
1、利用一个字段,即可存储经纬度;搜索时,只需一条索引,效率较高
2、编码的前缀可以表示更大的区域,查找附近的,非常方便。 SQL中,LIKE ‘wm3yr3%’,即可查询附近的所有地点。
3、通过编码精度可模糊坐标、隐私保护等。

缺点: 距离和排序需二次运算(筛选结果中运行,其实挺快)

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

相关链接

  1. 开发面试Hash原理详解
  2. 开发面试Hash面试考题
  3. Android CameraX 使用入门
  4. Finish和OnBackPressed、OnDestroy的区别

你可能感兴趣的:(常规基础篇)