首先来复习一下哈希的意思。
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射。
也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。因为散列值小于输入空间,所以也需要一定的解决冲突的算法来协调处理。常见的算法如:md5,sha等。
所有散列函数都有如下一个基本特性:如果两个散列值是不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。这个特性是散列函数具有确定性的结果,具有这种性质的散列函数称为单向散列函数。但另一方面,散列函数的输入和输出不是唯一对应关系的,如果两个散列值相同,两个输入值很可能是相同的。但也可能不同,这种情况称为“散列碰撞”,这通常是两个不同长度的散列值,刻意计算出相同的输出值。输入一些数据计算出散列值,然后部分改变输入值,一个具有强混淆特性的散列函数会产生一个完全不同的散列值。
这种散列算法就叫做哈希算法。
使用散列函数将键名和键值关联起来的数据结构就叫做哈希表。
一、哈希表
现在我们假设一个场景,来充分切入说明一下。
假设我有一个班级,一次考试后,得到所有学生的分数:
小明: 85
小红: 70
小张: 80
小王: 99
如上图所示,学生姓名经过hash函数处理后,映射到不同的数字上(当然实际上可能会出现冲突的情况,即散列碰撞,那么可以选择更改输入,重新散列获得新的散列值)。
然后我们将学生姓名映射得到的散列值和学生的分数一一映射起来,形成一个table,这便是散列表了。
0:
1: 70
2: 85
3:
4: 99
5: 80
6:
7:
8:
9:
如果我要查询某一个学生的分数,只需要这样:
hash(姓名) = 散列值
table(散列值) = 分数。
比如:hash(小明) = 2, table(2) = 85。
二、分布式哈希表(DHT: Distributed Hash Table)
OK,继续深入考虑,我们将散列表放在一个机器的内存里,当散列表比较小时候,没有问题,但如果这张散列表超过了一台机器的内存时候,
或者当存储在一台机器上时候,这台机器挂掉了,那所有的数据都会消失……
那现在我们又该怎么做呢?
这便需要引入DHT来处理这种情况了,说白了就是将一张哈希表分割在不同的机器上。
首先,将上面所说的散列空间0,1...9想像成一个首尾相衔的环,9之后又重新回到零,假设,这就是十台机器。
这样我们根据散列算法就可以将不同的学生映射到不同的机器上,实现了数据的分布。
三、一致性哈希(consistent hashing)
好了,上面我们使用DHT实现了数据的分布式存储,但再考虑深一层,分布式架构中,节点的故障是不可避免的,当添加和删除某一节点了,会导致大量散列数据失效,需要重新散列。
影响非常大,那我们用什么哈希算法来实现DHT才能尽量的避免这种情况呢,这便说到了一致性哈希。
consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在 key 映射关系。
刚刚我们把学生散列到了hash数值空间里,现在我们需要的是,同时将机器也散列在这个hash空间,让学生和机器的散列值同处在一个数值空间。
ok,如上图所示,如果我们将学生和机器同时散列在一个环中,那么假设小张散列后的值为6,我们顺时针寻找,寻找到的第一台机器,则将小张放入其中。
依次类推,则得到:
小王——>机器一
小明——>机器二
小红——>机器三
小张——>机器四
假设我们机器三失效了,那这时候影响到的仅仅是小红,重新散列到机器四中。
而如果重新加入一台新的机器,影响到的也仅仅是旁边的一台机器,这样便解决了添加删除机器时候的震荡问题,这便是一致性hash的大致思想。