简单说说一致性hash
关于hash
首先,什么是hash以及为什么要用hash?简单带过:
hash本身就是将目标对象或者数据片段映射到另外一个数据片段中.在编程环境下,通常意味着将一个数据或数值数据或字符串数据等等,映射为一个整型数值. 那么为何要用hash?很简单的一个原因:为了速度!为了方便!当然,这里指的是日常编程需要对hash的用法.除了方面访问数据,hash还有别的用途,比如在安全领域,可能需要对比如密码这样的重要数据进行加密,也有一些如md5,sha1这样的hash算法的应用,当然,此时的hash目标就不相同了!有着安全性如反破解等等考量.
简单hash的应用场景:
比如,我们有一组数据222,333,11,22,….,当我们需要经常查询这些数据的时候,比如查询这个数据是否存在,我们可以将他们存入一个list列表当中,但是这样的话进行查询时就需要按顺序查询,查询时间为O(N);但是,如果我们可以将他们看做是索引,那么就可以直接访问这个索引,查看数据是否存在等等.但是,就此而言,我们的数据可能是32位int或者64位int,如果直接作为索引看待,直接导致的问题就是内存消耗太大,并且空间利用率极低; 因此,我们可以考虑根据数据的数量来进行映射,比如数据数量为N,那么存储时可以根据hash(num) mod N 来进行存储.其中num为其中的数据.
以上,很简单,不再赘述.常见的hash使用,诸如各种高级编程语言实现的hash set,主要用于将数据存入一个集合set中,之后可以以O(1)的访问时间查看数据是否存在! 对于hash map或者hash table,则是将目标数据按照唯一的键进行存储,其值可以是任意的,然后可以根据key进行O(1)的快速访问.
好的,一致性hash!(分布式hash)
- 什么是一致性hash?
一致性哈希,主要应用于分布式领域.想象一下,如果我们有若干服务器,假设3个;然后我们有若干数据要存储,假设8个,那么我们可能使用最简单的方式进行存储,比如直接按照服务器的数量取模,然后分别存储各个数据到相应的服务器上.
我们使用分布式hash的目的是,为了加快数据访问速度,提升系统的性能.
首先:如果不适用分布式缓存,那么我可能需要直接从数据源读取数据,比如直接从数据库读取,一方面来说速度很慢,另一方面来说,在web环境下的巨大访问量,数据库可能会承受不住甚至crash.因此,分布式缓存的目的就是,一方面提升性能,另一方面缓解压力.客户端请求数据,首先会查询缓存,如果缓存中没有,我们称之为key miss,那么就需要从数据源读取数据,然后将其根据查询key缓存到缓存中.以便下次访问可以直接返回数据.
举例:
- 3个服务器A(0),B(1),C(2) 数据1,2,3,4,5,6,7,8;如何存储?
数据 | 对3取模 | 服务器 |
---|---|---|
1 | 1 | B |
2 | 2 | C |
3 | 0 | A |
4 | 1 | B |
5 | 2 | C |
6 | 0 | A |
7 | 1 | B |
8 | 2 | C |
好的,以上没啥问题!但是!万一要是其中一个服务器挂了呢?或者新增加了新的机器呢?OK,看看如果B挂了,会咋样?
A(0),C(1)
数据 | 对2取模 | 服务器 |
---|---|---|
1 | 1 | C |
2 | 0 | A |
3 | 1 | C |
4 | 0 | A |
5 | 1 | C |
6 | 0 | A |
7 | 1 | C |
8 | 0 | A |
我们发现,除了B的数据要发生迁移之外,其他服务器上的数据也要相应的迁移,由此带来了巨大的不便性!
一旦发生如上情况,就会出现大量的key miss问题,进而导致大量的数据源访问,并由此可能导致数据源服务器负载过量而crash.
Here comes consistent hash
于是乎,一致性哈希应运而生!
好的,开始想象如何解决以上的问题?也就是如何创建一致性hash算法.
- 在考虑一致性hash时,我们首先考虑将原始目标数据对象hash值映射到一个圆上,或者一个环上.
- 首先,假设我们的数据最大值为10.
- 然后,比如,对数据2,我们降到映射到360度的环上,它的度为2/10 * 360=72°;
- 接着,将我们的服务器也映射到这个环上,通常可以根据服务器的ip地址等进行hash映射,将他的hash值映射到这个环上,通常来说服务器的hash映射方法与数据的hash映射应当保持一致,这样可以使得数据更好的均匀分布到各个server上.这里,依旧可以假设,服务器的hash值依旧为0,1,2,那么据此,我们可以得到新的环映射表.
这里,对于数据和server映射的描绘用环图更合适,但是,为了偷懒,就用表吧 –)
数据或者服务器 | hash值 | 映射到环上angle |
---|---|---|
1 | 1 | 36° |
2 | 2 | 72° |
3 | 3 | 108° |
4 | 4 | 144° |
5 | 5 | 180° |
6 | 6 | 216° |
7 | 7 | 252° |
8 | 8 | 288° |
A | 0 | 0° |
B | 1 | 36° |
C | 2 | 72° |
* 实际上,server的hash值应该更均衡些,这样与数据之间才会更加均匀,emmm…先这样吧,,,
- 然后,我们就可以根据数据和服务器在环上的位置,按照增序的方式将数据放到server上,也就是每个数据顺时针查看server,并将其放置于第一个遇到的server上!
- 在实际编程实现中,我们可以将server放到一个list中,并从小到大排好序,然后对每一个数据,根据的他的度,查找list,直到遇到第一个比他大的,that’s it!如果,没有,那就循环放回第一个服务器节点!
- OK,Sounds easy enough!
虚拟节点问题
那么问题来了,很明显我们上面的三个服务器在这个环上很不均衡,因此我们通常会构造一些虚拟节点,到这个环上!比如每个节点构造10个,A0,A1,–A9,…B0—B9,…C0,..C9 ,然后也计算他们的hash值,并放置于环,然后对于数据查询存放位置为AX 的都放到服务器A上,以此类推. 关于虚拟节点的构造问题,还有一些技巧,据了解,对于5-10个server的,10个虚拟节点很好,对于50-100个server,可能需要100–500个…
Why? (一致性Hash的优点)
上面只说了一致性哈希的实现方案.但是还没说他为何这么实现呢?
好的,简答的分布式hash会带来因server crash或者新增server的数据重新调整存储位置的高昂代价.如果使用这个环形映射,当有服务器挂掉时,我们会发现,只有那个服务器上的数据会被分配到其他服务器的虚拟节点上(这也是虚拟节点的好处!试想,如果没有虚拟节点,那么某个节点服务器挂掉,其上所有数据都会被移送到下一个正常的服务器节点上,势必会让下一个服务器负载过重甚至crash!);当加入新的server时,也只会在加入的server的虚拟节点与其之前的虚拟节点之间发生数据迁移; 总的来说数据迁移量都在N/K的范围.[其中N为数据总量,K为服务器数量,或为初始服务器数,或为crash/新增后服务器数.]