一致性哈希算法(Consistent Hashing)

一致性哈希算法(Consistent Hashing )

一、问题

分布式架构中,无论是数据缓存还是持久化存储,都需要考虑负载均衡的问题,期望将对后台的访问或者数据存储能够尽可能地“平均”分发给每一个服务器节点。

为满足上述需求,最容易想到的一个办法就是hash,假设有N个节点,根据hash(obj)%N的结果来判断应该访问哪一个节点。理论上,如果能保证hash函数的随机性,则能够实现负载均衡的需求。然而,在实际情况中,节点具有不可预测的运行故障,如果集群中的某个节点宕机,那么节点数量就从N变成了N-1,若使用hash(obj)%N的方法实现负载均衡,此时将有(N-1)/N的数据发生迁移(即有(N-1)/N的数据hash(obj)%N != hash(obj)%(N-1),这个结论的推导文末会说明)。这将是一次规模很大的数据迁移,几乎之前所有的结果都将重新计算。同时,若集群规模不足以满足需求,需要扩充服务器节点时(节点数量从N变到N+1),也将发生类似问题。此时,基本的散列取余的方法非常低效。

上述问题其实是不满足hash函数的单调性,这是衡量hash函数好坏的一个指标。具体请见一致性hash(consistent hashing)

二、算法原理

2.1 算法简析

一致性哈希算法在1997年由麻省理工学院的Karger等人提出,用以解决分布式Cache的负载均衡问题。这是个能够保证单调性的hash算法。对于有N个节点的集群,最常见的hash(obj)%N算法首先计算出数据对象的hashcode,然后映射到0~N-1的N点环形空间中。而Consistent Hashing则将对象映射到0~232-1的环形空间中,同时注意,这里的对象包括服务器节点对象和数据对象。如下图所示:

consistent hashing的映射空间

为什么选择232这个数呢?因为hash算法所得结果一般是一个unsigned int类型。

因此,算法的步骤如下:

  1. 计算节点的hashcode,并将其映射在[0, 232-1]环形空间中(hash(node)%232)。
  2. 计算数据对象的hashcode,并映射到[0, 232-1]环形空间中(hash(obj)%232)。
  3. 在映射空间中,根据顺时针方向,数据对象找到离其最近的节点,即为该数据的存储(缓存)位置。

下图形象地描述了算法的原理。


consistent hashing 算法原理

CacheA, CacheB, CacheC表示三个节点,object1, object2, object3, object4表示四个数据对象。根据图中的映射结果,我们可以看出存储的分布为:{CacheA: [object1]}, {CacheB: [object4]}, {CacheC: [object2, object3]}。

那么回到一开始的问题,当节点增加或者减少时,Consistent Hashing算法是如何保证单调性的?

2.2 移除节点

假如此时CacheB节点宕机,从映射空间中移除。则根据顺时针查找的原理,object4将发现离它最近的节点变成了了CacheC,此时数据分布变成了:{CacheA: [object1]}, {CacheC: [object2, object3, object4]}。

移除节点

可以看出,只有object4发生了数据迁移,或者说只有CacheB存储的数据发生了迁移,而原本存储在其他节点上的数据依然维持原来的分布。

2.3 增加节点

当扩容时,增加一个节点CacheD,根据计算,映射在如下的位置上,则object2发现离他最近的节点变成了CacheD,则数据分布发生变化:{CacheA: [object1]}, {CacheB: [object4]}, {CacheC: [object3]}, {CacheD: [object2]}。

增加节点

此时,只有object2从CacheC迁移到CacheD上,而其他数据依然维持原来的分布关系。

这就满足了hash算法的单调性
内容通过hash算法分布到相应的节点中,若此时增加一个新节点,则旧节点中的内容要不就保持原来的分布关系,要不就分布到新增的节点中,而不会分布到其他的旧节点中。

2.4 虚拟节点(Virtual nodes)

为了衡量算法的稳定性,我们常常考虑一下极端场景。比如上面那张“移除节点”的图,当节点的数量比较少时,只有CacheA和CacheC,CacheA节点只存储一个数据,而CacheC却存储了3个数据,此时数据分布不均匀。

虽然是极端情况,但却很具备现实意义,毕竟常见的集群规模与232比都是微不足道了,极有可能发生上述的数据分布不均匀的情况,或者说平衡性问题

为此引入虚拟节点。

虚拟节点

如上图所示,CacheA2和CacheC2分别是CacheA1和CacheC1的虚拟节点,同样映射到了[0, 232-1]圆环空间。此时,逻辑上的数据分布为:{CacheA1: [object2]}, {CacheA2: [object1]}, {CacheC1: [object3]}, {CacheC2: [object4]}。

要注意的是,虚拟节点之所以是虚拟的,是因为它们仅仅是逻辑上存在的,数据存储的物理位置是其对应的真实节点。所以,物理上的数据分布为:{CacheA1: [object1, object2]}, {CacheC1: [object3, object4]}。

对比引入虚拟节点之前的数据分布:{CacheA1: [object1, object2, object4]}, {CacheC1: [object3]}。可以发现,虚拟节点的引入改善了consistent hashing算法的平衡性。

如果要引入虚拟节点,则引入的数量应当如何选择呢?

虚拟节点的引入数量

上图横轴为虚拟节点的倍数,纵轴为真实节点的个数。蓝线表示为保证hash算法的平衡性,节点数量与虚拟节点副本倍数的关系。仅供参考。

除了解决平衡性问题,节点的异构性也能通过引入虚拟节点加以解决。异构性通俗的讲就是不同服务器节点的存储、算力等性能是不一样的,一个完美的hash算法是连节点的异构性都能考虑进去的。根据每个节点的性能算出一个量化的权重,根据这个权重来计算这个节点引入虚拟节点的数量。性能差的,虚拟节点少点,分布的数据就少点,让他放轻松,别紧张。性能好的,就多整点,对着他干就对了。

三、代码实现

使用Java实现Consistent hashing。

package cn.edu.njupt.qyz;

import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;

public class ConsistentHashing<T> {

    // hash函数
    private final HashFunction hashFunction;
    // 虚拟节点副本数
    private final int numberOfReplicas;
    // 映射圆环
    private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>();

    // 构造方法
    public ConsistentHashing(HashFunction hashFunction, int numberOfReplicas, Collection<T> nodes) {
        this.hashFunction = hashFunction;
        this.numberOfReplicas = numberOfReplicas;

        for (T node : nodes) {
            add(node);
        }
    }

    // 将服务器节点映射到圆环上,同时加入对应的虚拟节点
    public void add(T node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            circle.put(hashFunction.hash(node.toString() + i), node);
        }
    }

    // 移除映射,同时移除虚拟节点
    public void remove(T node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            circle.remove(hashFunction.hash(node.toString() + i));
        }
    }
    // 根据传入的obj找到对应的节点
    public T get(Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        int hash = hashFunction.hash(key);
        if (!circle.containsKey(hash)) {
            // 从比obj的hashcode更大的Map中找,即顺时针方向找node
            SortedMap<Integer, T> tailMap = circle.tailMap(hash);
            // 环形空间,找到顺时针方向第一个node,并将其hashcode赋值
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        // 返回找到的节点
        return circle.get(hash);
    }
}

代码使用具有排序能力的的TreeMap实现环形映射结构,首先将节点加入到TreeMap中,然后定义一个get方法,根据传入的key对象来查找对应的节点。

四、总结

为了解决分布式架构的负载均衡问题,并保证hash算法的单调性,Consistent Hashing算法应运而生。同时,为了保证算法的平衡性,引入虚拟节点以达到令数据均匀分布在各个节点的目的。本文介绍了Consistent Hashing算法的基本原理,并进行了代码实现。

五、参考资料

  1. Consistent Hashing算法
  2. 一致性hash算法:jump Consistent hash(零内存消耗,均匀,快速,简洁)
  3. 一致性hash(consistent hashing)

推导

对hash(obj)%N的结果呈[0, N-1]循环,hash(obj)%(N-1)的结果为[0, N-2]的循环。N和N-1的最小公倍数为N(N-1),则二者的结果每隔N(N-1)个数据发生一次重叠,而每次重叠有N-1个结果是相等的,且有(N-1)2个结果不相等,因此全部的数据中有(N-1)/N的不相等,将发生数据迁移。可以参考下表。

hashCode N=2 N=3 N=4 N=5
0 0 0 0 0
1 1 1 1 1
2 0 2 2 2
3 1 0 3 3
4 0 1 0 4
5 1 2 1 0
6 0 0 2 1
7 1 1 3 2
8 0 2 0 3
9 1 0 1 4
10 0 1 2 0
11 1 2 3 1
12 0 0 0 2
13 1 1 1 3
14 0 2 2 4
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容