一致性哈希

    先介绍一个经典的服务器结构,有一个前端,前端服务器组可能就是专门用来分发用的,比如一个请求,举一个具体的例子,比如一个request,查一个姓名叫“zuo”的告诉年龄age是多少。这个往服务器上存的时候实际上推的是这么一个对儿,{“zuo”,31},也可以更新它,也可以拿出,“zuo”作为key,31作为value。一个经典的结构包括一个更复杂的request也是这样的:请求先到达前端服务器,前端服务器的某一台(这些分发服务器是无差别的,打到哪个上面都给提供相同的服务)。比如说一个域名,google.com,这个域名下面可能有一大堆机器,reques先通过dns走一圈,来到具体某个公司的request上,前端收到请求后会打到后端服务器组,后端服务器组就有讲究了,我可以认为任何一个request过来之后都会打到某一台前端服务器上,这个时候是无差别的,打到哪个服务器上提供的都是相同的服务。提供的服务是,(比如后端服务器有三台机器)通过key计算一下哈希得到一个哈希的返回值,hashvalue。把这个hashvalue%3,结果只可能是0,1,2。如果是0,就去0号机器上拿,如果是1,就去1号机器上拿,如果是2,就去2号机器上拿。如果想把{“zuo”,31}这个信息加入怎么加?先看这个请求的key是什么,打到0号机器还是1号机器还是2号机器,打到某台机器上加上这个信息。从中拿也是这么拿。由于哈希函数是非常均衡的,如果有很多字符串作为key的话,会负载均衡的分布在0,1,2机器上。这是一个经典的服务器负载结构。而且超级负载均衡。我们知道一个哈希值很高,模上3之后得到0或1或2的次数也基本是相同的,概率上是差不多的,当然它不是一个随机函数,但知道是啥意思就行了。

    这是个经典的服务器结构,之前大家都是这么做的,被大量使用,后来发现不好,因为当台数固定的时候是没什么问题的,但如果加一台呢,原来模3现在则应该模4,想让它继续负载均衡的话,得把0机器1机器2机器的所有的key拿出来重新计算一遍哈希,重新模4。比如0机器上的消息,具体一个key应该把它分到哪去呢,就得重新算一遍,所以加机器减机器的时候就不行了,改代码还是其次,问题是数据迁移的代价太高了。把所有数据的key拿出来重新取哈希再模4,才能得到数据新的归属在这4台机器的哪一台,所以这是一个问题。而一致性哈希这个结构可以帮助解决这个问题。

而一致性哈希是个什么结构呢?前端是无差别的,也可以认为每一个request打到前端所提供的服务是一样的,就是告诉它到后端的哪一台上去。前端没有任何变化。原来的0号桶,1号桶,2号桶是固定的,而一致性哈希结构不是这样的,hash函数的输入是任意一个字符串,字符串的数量是很多的,可以认为是无穷大的一个量级,哈希的返回值假设是0到2的64次方的范围。那么一致性哈希的结构,就可以把整个哈希域想象成一个环,0号位置的下一个位置就是1号位置,再下一个位置就是2号位置,转一圈到2的64次方位置,2的64次方的下一个位置是0号位置,这个环是首尾相接的,在这种结构下,先别管前端服务器,先看后端的。假设有三台机器,m1,m2,m3,后端这三台机器肯定有专属于每台机器的信息,比如说m1,m2和m3的ip肯定就不一样,mac地址肯定就不一样,hostname肯定也不一样,这些不一样的信息假设是ip,那么m1就有个ip1,m2就有个ip2,m3就有个ip3,反正肯定不一样。把m1的ip1通过哈希函数算出一个哈希值,打到环上去;把m2的ip2通过哈希函数算出一个哈希值,打到环上去;把m3的ip3通过哈希函数算出一个哈希值,打到环上去;假设打到环上的不同三个位置。当一个request从前端服务器组到来的时候,前端算出一个哈希值,记为hash_request1,这个哈希值肯定对应环上某个位置,假设打到m1和m2之间的一个位置,那么request去哪台机器上处理请求呢,去顺时针遇到的第一台机器。

在实践过程中,其实很好做,m1,m2,m3各自算出的哈希值h1,h2,h3将其排序,假设排序后是【16,120亿,1024亿】这样三个哈希值,把排序后每一个数组放到前端代理服务器里。每个代理服务器都存着一个有序数组,服务器端有几台机器,这个数组就有多少个值。而且是排序之后的。那么一个request到来之后怎么顺时针的找,假设前端服务器拿到一个request算出一个哈希值是1023亿,其实就是在这个有序数组中二分的找刚好比它大的哈希值。那么就找到了1024亿。那么就去1024亿这个哈希值代表的机器上去处理这个请求。如果是1025亿,没有比它刚刚好大的值,那就应该顺着环去16这个哈希值代表的机器。

前端搞定了看后端,在这种情况下要是想加一台机器就很容易,数据迁移代价就很小,比如要加一台m4,m4算出哈希值h4,打到环上的某个位置上,m4要进这个环要做什么操作呢?首先m4算出的哈希值应该在前端服务器的有序数组里面加上它,所以在后端服务器要做的操作就是,m4上了一个环,只用找到m4顺时针的第一台机器,即m3,把原本m3负责的某一段数据传给m4,m3删掉那些数据,m4留着就行了。以后这一部分的数据就m4处理了。其实就是m3的某部分数据拿给m4就可以了,剩下的都不用变,这就是加机器的时候进行的数据迁移的代价,是很低的。

同理原来是四台机器想要删掉一台m4要怎么删呢,就把m4上所有的数据给到m3就可以了,可见减机器的时候也很方便。

但问题在于:1.哈希函数只有在量起来的时候,哈希函数的特征才能起来,如果一开始就是三台机器或者两台机器,初始机器数量很少的时候,就不能保证整个环被均分。因为它是个离散函数,可能两个点在环上打的特别近,不保证一定能打到对侧均分这样。而环不均也就意味着机器负载不均衡了。

2.即便能够解决第一个问题,在两台或者三台的时候假如你用某种魔幻的方式做到了把环均分,那加机器的时候也照样会破坏均衡性。

这两个问题是哈希函数的性质带来的。如果一个房间只给你有限个香水分子那它们分布肯定是不均匀的,但是香水量大了之后就能保证均匀了,基本上你在房间的任何一个角落闻到香水的浓度应该都是差不多的。也就是说,哈希函数的性质使得少量的时候不保证均衡,即便保证均衡了,加台机器又不均衡了。

这两个问题用虚拟节点技术可以解决。假设有三台机器,m1,m2,m3,还是去抢一个环,但方式变了。假设m1这台机器,不让物理机去抢这个环,而是给它分配一万个虚拟节点,V1-1,V1-2,V1-3...V1-10000,这是一张路由表,通过m1可以查有哪些虚拟节点,通过一个虚拟节点可以查它属于哪个物理机器。m2,同理,m3同理都分别分配一万个虚拟节点,然后让这三万个虚拟节点去抢这个环,V1-1到V1-10000的每个虚拟节点负责的域都归在m1上。m2,同理,m3同理。那么m1,m2,m3就把环均分了。因为量起来了很大,有三万个节点。

再说一下工程落地,m1怎么分10000个虚拟节点呢?我们总能拿到一些专属信息。比如m1的ip地址196.125.1.1,就把196.125.1.1-1算出一个哈希值就作为虚拟节点V1-1的哈希值,196.125.1.1-2算出一个哈希值就作为虚拟节点V1-2的哈希值,一直到196.125.1.1-10000算出一个哈希值就作为虚拟节点V1-10000的哈希值。

有了虚拟节点技术,初始机器量少以及加减机器的问题全部解决了。这个技术有应用到很多地方,只要任何一个有分布式系统有服务器概念的地方都被它改造过,亚马逊的DynamoDB就是用这个做的。spark有一些服务也是用这个技术做的。只要是牵涉到分布式要共同负载的东西,一致性哈希用到了很多。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值