首页 > 编程知识 正文

一致性hash算法 java,负载均衡一致性hash算法

时间:2023-05-05 09:06:42 阅读:165376 作者:1206

要了解一致的dzdxn,必须首先了解传统的dzdxn及其在大型分布式系统中的局限性。 简而言之,dzdxn是键值对的存储,在给定密钥的情况下,可以非常高效地找到相关值。 假设根据邮政编码查找城市街道的名称。 最简单的实现方法之一是将此信息以dzdxn词典的形式存储在Zip Code,Street Name中。

如果数据太大而无法存储在一个节点或计算机上,问题会变得更有趣,系统需要多个节点或计算机来存储它。 例如,使用多个Web缓存中间件的系统。 给定那如何确定哪个 key 存储在哪个节点上?针对该问题,最简单的解决方案是使用dzdxn取模来确定key,首先对key执行dzdxn运算,将其除以系统中的节点数,然后将该key放入该节点。 同样,检索key时,对key执行dzdxn运算,除以节点数,然后移动到该节点以检索值。 对应于上述过程的dzdxn算法定义如下:

node_number=hash(key ) % N #其中n是节点数。 以下图显示了多节点系统中传统的dzdxn仿真算法,基于该算法可以实现简单的负载均衡。

同时,低于传统dzdxn调制算法的局限性,分析了传统dzdxn及其大规模分布式系统中的局限性。 这里直接使用我以前写的文章布隆过滤器的你值得拥有的开发利器定义的SimpleHash类,然后分别对semlinker、kakuqo和test 3这三个键进行dzdxn运算,取余数具体代码如下。

publicclasssimplehash { private int cap; 私有输入种子; publicsimplehash(intcap,int seed ) { this.cap=cap; this.seed=seed; }publicinthash(stringvalue ) { int result=0; int len=value.length (; for(intI=0; i len; I ) result=seed * result value.charat (I; }返回(cap-1 )返回; } publicstaticvoidmain (string [ ] args ) simplehashsimplehash=newsimplehash (212,8 ); system.out.println (node _ number=hash (SEM linker ) )3- ) simple hash.hash (SEM linker ) )3); system.out.println (' node _ number=hash ((kakuqo ) )3-'simplehash.hash ) (kaku qo ) )3); system.out.println (node _ number=hash ((test ) )3- ) simplehash.hash ) (test )3); }如果上述代码成功执行,控制台将输出以下结果:

node_number=hash(SEMlinker ) (3-1node_number ) hash (kaku qo ) )3-2node_number=hash ) (test ) ) )。

1.1节点减少的场景在分布式多节点系统中经常出现故障。 任何节点都可能在没有事先通知的情况下挂起。 对于这种情况,我们期待只会降低系统性能,而不会影响正常功能。 在原示例中,如果节点发生故障会怎么样? 假设原始示例中的三个节点中有一个节点出现故障,则节点数将发生变化,节点数将从3减少到2,表的状态将发生变化。

明显地,节点的减少改变了密钥和节点的映射关系,这种变化对新密钥没有任何影响,但对现有密钥来说会引起节点的映射错误。 在“semlinker”示例中,变化前的系统有三个节点,其密钥对应的节点号为1,发生故障时节点数减少两个,该密钥对应的节点号为0。

1.2节点增加的场景分布式多节点系统需要针对特定场景(如节日)扩展服务节点以应对突发流量。 在原始示例中,添加节点会发生什么情况? 在原始示例中,假设有三个节点为了进行扩展而临时增加了一个节点。 此时,节点数发生了变化,节点数从3增加到了4个,表的状态发生了变化。

很明显,随着节点的增加,键和节点的映射关系也会发生变化。 这一变化对新密钥没有任何影响,但对现有密钥来说是节点映射

射错误,同样以 “semlinker” 为例,变化前系统有 3 个节点,该键对应的节点编号为 1,当增加节点时,节点数增加为 4 个,此时该键对应的节点编号为 2。

当集群中节点的数量发生变化时,之前的映射规则就可能发生变化。如果集群中每个机器提供的服务没有差别,这不会有什么影响。但对于分布式缓存这种的系统而言,映射规则失效就意味着之前缓存的失效,若同一时刻出现大量的缓存失效,则可能会出现 “缓存雪崩”,这将会造成灾难性的后果。

要解决此问题,我们必须在其余节点上重新分配所有现有键,这可能是非常昂贵的操作,并且可能对正在运行的系统产生不利影响。当然除了重新分配所有现有键的方案之外,还有另一种更好的方案即使用一致性dzdxn算法。

二、一致性dzdxn算法

一致性dzdxn算法在 1997 年由麻省理工学院提出,是一种特殊的dzdxn算法,在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性dzdxn解决了简单dzdxn算法在分布式dzdxn表(Distributed Hash Table,DHT)中存在的动态伸缩等问题 。

2.1 一致性dzdxn算法优点 可扩展性。一致性dzdxn算法保证了增加或减少服务器时,数据存储的改变最少,相比传统dzdxn算法大大节省了数据移动的开销 。更好地适应数据的快速增长。采用一致性dzdxn算法分布数据,当数据不断增长时,部分虚拟节点中可能包含很多数据、造成数据在虚拟节点上分布不均衡,此时可以将包含数据多的虚拟节点分裂,这种分裂仅仅是将原有的虚拟节点一分为二、不需要对全部的数据进行重新dzdxn和划分。
虚拟节点分裂后,如果物理服务器的负载仍然不均衡,只需在服务器之间调整部分虚拟节点的存储分布。这样可以随数据的增长而动态的扩展物理服务器的数量,且代价远比传统dzdxn算法重新分布所有数据要小很多。 2.2 一致性dzdxn算法与dzdxn算法的关系

一致性dzdxn算法是在dzdxn算法基础上提出的,在动态变化的分布式环境中,dzdxn算法应该满足的几个条件:平衡性、单调性和分散性。

平衡性:是指 hash 的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题。单调性:是指在新增或者删减节点时,不影响系统正常运行。分散性:是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据。 三、一致性dzdxn算法原理

一致性dzdxn算法通过一个叫作一致性dzdxn环的数据结构实现。这个环的起点是 0,终点是 2^32 - 1,并且起点与终点连接,故这个环的整数分布范围是 [0, 2^32-1],如下图所示:

3.1 将对象放置到dzdxn环

假设我们有 “semlinker”、“kakuqo”、“lolo”、“fer” 四个对象,分别简写为 o1、o2、o3 和 o4,然后使用dzdxn函数计算这个对象的 hash 值,值的范围是 [0, 2^32-1]:

图中对象的映射关系如下:

hash(o1) = k1; hash(o2) = k2;hash(o3) = k3; hash(o4) = k4; 3.2 将服务器放置到dzdxn环

接着使用同样的dzdxn函数,我们将服务器也放置到dzdxn环上,可以选择服务器的 IP 或主机名作为键进行dzdxn,这样每台服务器就能确定其在dzdxn环上的位置。这里假设我们有 3 台缓存服务器,分别为 cs1、cs2 和 cs3:

图中服务器的映射关系如下:

hash(cs1) = t1; hash(cs2) = t2; hash(cs3) = t3; # Cache Server 3.3 为对象选择服务器

将对象和服务器都放置到同一个dzdxn环后,在dzdxn环上顺时针查找距离这个对象的 hash 值最近的机器,即是这个对象所属的机器。 以 o2 对象为例,顺序针找到最近的机器是 cs2,故服务器 cs2 会缓存 o2 对象。而服务器 cs1 则缓存 o1,o3 对象,服务器 cs3 则缓存 o4 对象。

3.4 服务器增加的情况

假设由于业务需要,我们需要增加一台服务器 cs4,经过同样的 hash 运算,该服务器最终落于 t1 和 t2 服务器之间,具体如下图所示:

对于上述的情况,只有 t1 和 t2 服务器之间的对象需要重新分配。在以上示例中只有 o3 对象需要重新分配,即它被重新到 cs4 服务器。在前面我们已经分析过,如果使用简单的取模方法,当新添加服务器时可能会导致大部分缓存失效,而使用一致性dzdxn算法后,这种情况得到了较大的改善,因为只有少部分对象需要重新分配。

3.5 服务器减少的情况

假设 cs3 服务器出现故障导致服务下线,这时原本存储于 cs3 服务器的对象 o4,需要被重新分配至 cs2 服务器,其它对象仍存储在原有的机器上。

3.6 虚拟节点

到这里一致性dzdxn的基本原理已经介绍完了,但对于新增服务器的情况还存在一些问题。新增的服务器 cs4 只分担了 cs1 服务器的负载,服务器 cs2 和 cs3 并没有因为 cs4 服务器的加入而减少负载压力。如果 cs4 服务器的性能与原有服务器的性能一致甚至可能更高,那么这种结果并不是我们所期望的。

针对这个问题,我们可以通过引入虚拟节点来解决负载不均衡的问题。即将每台物理服务器虚拟为一组虚拟服务器,将虚拟服务器放置到dzdxn环上,如果要确定对象的服务器,需先确定对象的虚拟服务器,再由虚拟服务器确定物理服务器。

图中 o1 和 o2 表示对象,v1 ~ v6 表示虚拟服务器,s1 ~ s3 表示物理服务器。

四、一致性dzdxn算法实现

这里我们只介绍不带虚拟节点的一致性dzdxn算法实现:

import java.util.SortedMap;import java.util.TreeMap;public class ConsistentHashingWithoutVirtualNode { //待添加入Hash环的服务器列表 private static String[] servers = {"192.168.0.1:8888", "192.168.0.2:8888", "192.168.0.3:8888"}; //key表示服务器的hash值,value表示服务器 private static SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>(); //程序初始化,将所有的服务器放入sortedMap中 static { for (int i = 0; i < servers.length; i++) { int hash = getHash(servers[i]); System.out.println("[" + servers[i] + "]加入集合中, 其Hash值为" + hash); sortedMap.put(hash, servers[i]); } } //得到应当路由到的结点 private static String getServer(String key) { //得到该key的hash值 int hash = getHash(key); //得到大于该Hash值的所有Map SortedMap<Integer, String> subMap = sortedMap.tailMap(hash); if (subMap.isEmpty()) { //如果没有比该key的hash值大的,则从第一个node开始 Integer i = sortedMap.firstKey(); //返回对应的服务器 return sortedMap.get(i); } else { //第一个Key就是顺时针过去离node最近的那个结点 Integer i = subMap.firstKey(); //返回对应的服务器 return subMap.get(i); } } //使用FNV1_32_HASH算法计算服务器的Hash值 private static int getHash(String str) { final int p = 16777619; int hash = (int) 2166136261L; for (int i = 0; i < str.length(); i++) hash = (hash ^ str.charAt(i)) * p; hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; hash ^= hash >> 17; hash += hash << 5; // 如果算出来的值为负数则取其绝对值 if (hash < 0) hash = Math.abs(hash); return hash; } public static void main(String[] args) { String[] keys = {"semlinker", "kakuqo", "fer"}; for (int i = 0; i < keys.length; i++) System.out.println("[" + keys[i] + "]的hash值为" + getHash(keys[i]) + ", 被路由到结点[" + getServer(keys[i]) + "]"); }}

以上代码成功运行后,在控制台会输出以下结果:

[192.168.0.1:8888]加入集合中, 其Hash值为1326271016[192.168.0.2:8888]加入集合中, 其Hash值为1132535844[192.168.0.3:8888]加入集合中, 其Hash值为115798597[semlinker]的hash值为1549041406, 被路由到结点[192.168.0.3:8888][kakuqo]的hash值为463104755, 被路由到结点[192.168.0.2:8888][fer]的hash值为1677150790, 被路由到结点[192.168.0.3:8888]

上面我们只介绍了不带虚拟节点的一致性dzdxn算法实现,如果有的小伙伴对带虚拟节点的一致性dzdxn算法感兴趣,可以参考 一致性Hash(Consistent Hashing)原理剖析及Java实现 这篇文章。

五、总结

本文通过示例介绍了传统的dzdxn取模算法在分布式系统中的局限性,进而在针对该问题的解决方案中引出了一致性dzdxn算法。一致性dzdxn算法在 1997 年由麻省理工学院提出,是一种特殊的dzdxn算法,在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。在介绍完一致性dzdxn算法的作用和优点等相关知识后,我们以图解的形式生动介绍了一致性dzdxn算法的原理,最后给出了不带虚拟节点的一致性dzdxn算法的 Java 实现。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。