22.1.21 与哈希函数有关的结构
1 Hash函数的性质:
-
out = f(in),其中in是无穷尽的,out是有穷尽的;
-
相同的输入经历Hash函数后能够得到相同的输出;
-
在很小的几率下,不同的输入也能得到相同的输出,概率很低;
-
out具有均匀性和离散性。
2 哈希表
1)经典的哈希表可以看作是由哈希函数得到的不同的哈希key,再用这些哈希key组成一个一个的桶,桶上挂有单链表的逻辑结构。哈希表在实际使用的时候增删改查近似为O(1),理论值为O(logn)。
2)有关哈希表的扩容技术:
-
扩容技术1:设置一个临界值value,当某条单链表的长度即将超过临界值时触发扩容。
-
时间复杂度分析:假设有n个字符串,在最坏的情况下,即单链的长度超过2就触发扩容,则最差扩容logn次,一次扩容的代价为O(n),因为扩容后已经储存在哈希表里的值都要重新计算哈希key,重新放桶,则n次扩容的总代价为O(nlogn),那么单词扩容的代价为O(nlogn)/n = O(logn)。
-
-
扩容技术2:在虚拟机托管的语言中,可以用离线扩容。虚拟机不占用用户的在线时间,生成一个新的容量更大哈希表,用户下次在使用哈希表时就是使用的新表。
3 布隆过滤器
1)布隆过滤器用来解决类似黑名单和爬虫去重的问题(同时不具备删除行为的问题)。他的优点时极大的减少了内存的使用,允许有一定的失误。
2)位图(bit map)
-
位图是利用基本类型改编而来的一种数据结构。例如:int arr[10] 表示一个长度为10的int型数组,他占4 * 8 * 10 = 320bits,所以他也能表示bit[320],这个bit[320]就是位图。
-
在int arr[10]中,存储数的状态是通过0 or 1来完成的,利用int numindex = x/32可以得到x这个数应该存储在10个位置中的第几位,再利用int bitindex = x%32可以得到x实在某一数位中的准确字节位(一个数位包含32个字节位)。通过获得这个字节位上的值是0还是1我们就能知道这个数的存储状态。
-
位图有两种基本的操作:查询和修改。
-
code:
//查询
int search(int x)
{
int numindex = x/32;
int bitindex = x%32;
int flag = (arr[numindex]>>bitindex) &1;//此时flag的返回值就是x的存储状态。
return flag;
}
//将第x比特位上的数状态改成1
arr[numindex] = arr[numindex] | (1<<bitindex);//把1的二进制表示中的1左移bitindex位,这个1与或上另外一个数恒为1.
//将第x比特位上的数状态改成0
arr[numindex] = arr[numindex] | (~(1<<bitindex));
3)举例说明布隆过滤器的作用
网页黑名单,假设有100亿个禁止访问的URL,用户在访问一个网址时系统要判断这个网址可不可以访问。
设置k个hash函数,对每一个UML|都利用k个Hash函数处理得到k个输出m1~mk,把结果对m取余得到输出n,在k大小的位图中对结果进行描黑(或者把该数位上的状态改成1)则表示这个UML不能访问。查询某个UML x能不能访问时,对x分别调用这k个Hash函数得到k的输出,在取余得到结果,对每一个结果在位图上查询状态,有一个不为1则说明可以访问。全为1说明不能访问。
4)哈希函数的数量和位图的大小共同决定着布隆过滤器的失误率,在一定范围内,Hash函数越多失误率越低,但太多的hash函数会使位图的使用率上升(可以理解为位图在当前容量下太小了),导致失误率上升。位图的容量越大,失误率越低。
5)设计布隆选择器
-
看有没有删除需求,允不允许有一定的失误,失误率是多少?知道样本容量n和失误率p;
-
位图的空间m = -((n*lnp)/(ln2)^2);
-
Hash函数的个数k = ln2*m/n(向上取整);
-
实际失误率 p' = (1-e^(-(n*k)/m))^k;
4 一致性Hash
一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性hash时采用如下步骤:
-
首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
-
然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
-
然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
-
一致性哈希算法(Consistent Hashing)最早在论文《
整个空间按顺时针方向组织。0和232-1在零点中方向重合。
下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置,这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下:
接下来使用如下算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
例如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:
根据一致性哈希算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。
下面分析一致性哈希算法的容错性和可扩展性。现假设Node C不幸宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
下面考虑另外一种情况,如果在系统中增加一台服务器Node X,如下图所示:
此时对象Object A、B、D不受影响,只有对象C需要重定位到新的Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
另外,一致性哈希算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。例如系统中只有两台服务器,其环分布如下,