一致性哈希 (Consistent Hashing) 原理及Java实现

外网上一篇关于一致性哈希讲得很清晰的文章,翻译记录一下。
原文地址:https://tom-e-white.com/2007/11/consistent-hashing.html

*根据博主习惯,下文中部分位置保留原英文词

原理

对于一致性哈希(Consistent Hashing)的需求随着运行缓存机群组时的限制而浮现。如果你有一组 n 台的缓存机,那么做负载均衡的一个普遍方法是将object o放在第 hash(o) mod n 台机器。然而当你增加或移除缓存机时,n 发生变化,导致每个对象都会被hash到新的位置。这可能造成灾难性的后果,存储源数据的机器会突然收到大量缓存机的请求,就好像所有缓存突然消失了一样。

我们希望的是,当一台缓存机加入时,它从其他机器承担一部分合理的负载。同样的,当一台缓存机被移除时,它承载的负载被分配到其它机器上。这就是一致性哈希所做的——尽可能 一致地 将同一对象映射到同一台缓存机。

一致性哈希背后的理念是使用hash函数同时对object和cache做hash计算。这样做的目的是将cache映射到一个区间(interval),该区间将容纳数个object的哈希。如果cache被移除,这个区间内的object将被相邻的cache区间承担。其他所有cache都不会改变。

具体来说,hash函数通常将object和cache映射到一个数字区间。所有Java程序员都应该对此感到熟悉——Object类的hashCode方法返回一个int值(-231 to 231-1)。想象将这个范围放到一个圈上,数字落在圈上的点。object(1,2,3,4)和cache(A,B,C)被标记在他们hash值的位置。
在这里插入图片描述
为了找到一个object所在的cache,我们从object位置出发,顺时针前进直到找到一个cache点。所以上图中,object 1和4落在cache A,object 2落在cache B,object 3落在cache C。考虑如果C被移除会发生什么:object 3现在属于cache A,其他object的映射都没有改变。接下来如果cache D被加到图中所示位置,它会接管object 3和4。
在这里插入图片描述
以上系统能良好运行有一个条件:所有cache应被均匀地击中。然而由于随机性(译注:并且物理cache节点相对object节点数量很少),有可能出现objects极不均匀分布于cache上的情况。解决方案是引入“虚拟节点”的概念,也就是圈上cache点的复制。每当加入物理节点时,我们创建数个虚拟节点并实际加入圆环。

下图可以看出这样做的显著效果。我在10个cache上模拟存储了10,000个objects。x轴代表cache的复制数(对数规模)。当该值较小时,可以看到objects在caches上的分布是不均衡的(y轴上显示的标准差很大)。当复制数增加时,objects的分布明显更均匀了。这个实验表明100至200的复制数能达到一个可接受的平衡度(标准差为5%-10%平均值)。
在这里插入图片描述

实现

下面是一份简单的Java实现。为了一致性哈希能有效实现,使用一个能充分打乱的hash函数非常重要。然而ObjecthashCode方法并不能很好做到这一点——它们一般产生一组较小的integer——所以我们使用HashFunction接口以允许使用自定义的哈希函数。这里推荐MD5哈希。

译注:

  1. MD5哈希得到128-bit串,一般以32位十六进制数表示,如果需要得到数字值(例如,Java中long, 64位),需要进行截断等处理。也可以考虑使用FNV,Murmur等算法直接实现字符串到N位数字的哈希。
  2. 作者此处未给MD5哈希的实现代码,可以参考github上这位作者的实现。

HashFunction接口:

public interface HashFunction {
    Integer hash(Object key);
}

ConsistentHash.java:

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

public class ConsistentHash<T> {

    private final HashFunction hashFunction;
    private final int numberOfReplicas;
    private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>();

    public ConsistentHash(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));
        }
    }

    public T get(Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        int hash = hashFunction.hash(key);
        if (!circle.containsKey(hash)) {
            SortedMap<Integer, T> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值