分布式哈希表(DHT)
普通的Hash方式
普通的Hash的特点:
创建哈希表(HashMap)需要先指定大小,即默认创建一个能够存储多少个元素的哈希表。
当不断地向HashMap中添加元素时,HashMap越来越满,当添加的元素达到了装载因子乘以表长时,就需要扩容了。扩容时,原来已经映射到哈希表中的某个位置(桶)的元素需要重新再哈希,然后再把原来的数据复制到新的哈希表中。
对于普通的哈希表而言,扩容的代价是很大的。
例子
普通的Hash计算地址方式如下:Hash(Key)%M,
假设哈希函数为 hash(x)=x ,哈希表的长度为5(有5个桶)。
- key=6时,hash(6)%5 = 1,即Key为6的元素存储在第一个桶中
- key=7时,hash(7)%5 = 2,即Key为7的元素存储在第二个桶中
- Key=13时,hash(13)%5=3,即Key为13的元素存储在第三个桶中
假设现在hash表长度扩容成8,那么Key为6,7,13 的数据全都需要重新哈希。
- key=6时,hash(6)%8 = 6,即Key为6的元素存储在第六个桶中
- key=7时,hash(2)%8 = 7,即Key为7的元素存储在第七个桶中
- Key=13时,hash(13)%8=5,即key为13的元素存储在第五个桶中
从上可以看出:扩容之后,元素的位置全变了。比如:Key为6的元素原来存储在第一个桶中,扩容之后需要存储到第6个桶中。
因此,这是普通哈希的一个不足:扩容可能会影响到所有元素的移动。这也是为什么:为了减少扩容时元素的移动,总是将哈希表扩容成原来大小的两倍的原因。因为,有数学证明,扩容成两倍大小,使得再哈希的元素个数最少。(?)
一致性哈希方式
在分布式系统中,节点的宕机、某个节点加入或者移出集群是常事。对于分布式存储而言,假设存储集群中有10台机子,如果采用Hash方式对数据分片(即将数据根据哈希函数映射到某台机器上存储),哈希函数应该是这样的:hash(file) % 10。
根据上面的介绍,扩容是非常不利的,如果要添加一台机器,很多已经存储到机器上的文件都需要重新分配移动,如果文件很大,这种移动就造成网络的负载。
因此,就出现了一种一致性哈希的方式。一致性哈希,其实就是把哈希函数可映射的空间(相当于普通哈希中桶的数目是固定的)固定下来了,比如固定为: 2n−1 ,并组织成环的形状。
每个机器对应着一个n位的ID,并且映射到环中。每个查询键,也是一个 n 位的ID,节点的ID和查询键对应着相同的映射空间。
Each node choose a n-bit ID
// ID是随机的,所以所有的node是随机分布在环上
Intention is that they be random Though probably a hash of some fixed info IDs are arranged in a ring
// key的哈希跟ID属于同一个域内,也是分布在同一个环上
Each lookup key is also a n-bit ID
I.e., the hash of the real lookup key
Node IDs and keys occupy the same space!
例子
如下图:有四台机器映射到固定大小为