分布式一致性Hash算法实现

时间:2021-09-15 09:48:53

使用Redis作为缓存服务器的,刚开始的时候会满足需要,随着项目的增大缓存数据的增多就会查询和插入更慢这时就要考虑Redis集群方案了

使用Redis分布式要保证数据都能能够平均的缓存到每一台机器,首先想到的做法是对数据进行分片,因为Redis是key-value存储的,首先想到的是Hash分片,可能的做法是对key进行哈希运算,得到一个long值对分布式的数量取模会得到一个一个对应数据库的一个映射,没有读取就可以定位到这台数据库,那么速度但然会提升了。

但是取模的hash算法是有问题的如果集群数量不变的话没有什么问题,一旦增加一台机器或者一台机器挂掉,导致机器数量变化,就会导致计算的出的数据库映射乱掉,不能正确存取数据了。

因为这个问题引入我们说的一致性哈希算法,这个哈希算法具有的特征

1.均衡性:也有人把它定义为平衡性,是指哈希的结果能够尽可能分布到所有的节点中去,这样可以有效的利用每个节点上的资源。

2.单调性:对于单调性有很多翻译让我非常的不解,而我想要的是当节点数量变化时哈希的结果应尽可能的保护已分配的内容不会被重新分派到新的节点。

3.分散性和负载:这两个其实是差不多的意思,就是要求一致性哈希算法对 key 哈希应尽可能的避免重复。

一致性哈希就数据结构是创建一个排序的环形数据结构,有许多个区域,先让每一台服务器都分布环上,取每一个服务器的特效做哈希运行,得到的值放进环中,进行排序这样就能根据哈希特征找到对应的真是服务器,能够让把服务器平均的分布到环上。



第一个特征均衡性:就是尽量的让数据平均的分部到每一个服务器,不让某台机器压力特别打,或者干脆没活干,因为这个原因,我们的每一个服务器都添加几个虚拟服务器,比如真是服务器叫node1那么第一个服务器的虚拟服务器就叫node1-1,node1-2...,根据这些特征进行哈希运算也分布到环中,这样就能把服务器平均的分布到环中。


第二个特征单调性:因为服务器都在环中,数据的key进行哈希运算得到一个值,跟环中的服务器的哈希值进行比较,取离当前值最接近的哈希值对象的服务器,这样就是获取服务器的原理了,我们是做了一个偷懒的工作,服务器哈希进行排序,以顺时针方式得到一个刚好大于key哈希的服务器。
单调性是在不管添加节点还是删除节点,原来对应的服务器不变,因为这个环很大,服务器是零星分布的,这样增加或者删除一个节点只有受影响的都是当前节点,但是key对应的数据库是不变的,也不能说不变,是把变化变得尽可能的小。

第三个特征分散性和负载:指服务器在环中尽可能的分散,尽可能的让数据平均分布到不同的服务器,我们就是使用虚拟节点的方式解决的。

public final class MurmurHash {  
  
    public MurmurHash() {  
  
    }  
  
    private byte[] toBytesWithoutEncoding(String str) {  
  
        int len = str.length();  
        int pos = 0;  
        byte[] buf = new byte[len << 1];  
        for (int i = 0; i < len; i++) {  
  
            char c = str.charAt(i);  
            buf[pos++] = (byte) (c & 0xFF);  
            buf[pos++] = (byte) (c >> 8);  
        }  
        return buf;  
    }  
  
    public int hashcode(String str) {  
        byte[] bytes = toBytesWithoutEncoding(str);  
        return hash32(bytes, bytes.length);  
    }  
  
    /** 
     *  * Generates 32 bit hash from byte array of the given length and  * seed. 
     *  *  * @param data byte array to hash  * @param length length of the array 
     * to hash  * @param seed initial seed value  * @return 32 bit hash of the 
     * given array   
     */  
    public int hash32(final byte[] data, int length, int seed) {  
        // 'm' and 'r' are mixing constants generated offline.  
        // They're not really 'magic', they just happen to work well.  
        final int m = 0x5bd1e995;  
        final int r = 24;  
        // Initialize the hash to a random value  
        int h = seed ^ length;  
        int length4 = length / 4;  
        for (int i = 0; i < length4; i++) {  
            final int i4 = i * 4;  
            int k = (data[i4 + 0] & 0xff) + ((data[i4 + 1] & 0xff) << 8)  
                    + ((data[i4 + 2] & 0xff) << 16)  
                    + ((data[i4 + 3] & 0xff) << 24);  
            k *= m;  
            k ^= k >>> r;  
            k *= m;  
            h *= m;  
            h ^= k;  
        }  
        // Handle the last few bytes of the input array  
        switch (length % 4) {  
        case 3:  
            h ^= (data[(length & ~3) + 2] & 0xff) << 16;  
        case 2:  
            h ^= (data[(length & ~3) + 1] & 0xff) << 8;  
        case 1:  
            h ^= (data[length & ~3] & 0xff);  
            h *= m;  
        }  
        h ^= h >>> 13;  
        h *= m;  
        h ^= h >>> 15;  
        return h;  
    }  
  
    /** 
     *  * Generates 32 bit hash from byte array with default seed value.  *  * @param 
     * data byte array to hash  * @param length length of the array to hash  * @return 
     * 32 bit hash of the given array   
     */  
    public int hash32(final byte[] data, int length) {  
        return hash32(data, length, 0x9747b28c);  
    }  
      
    public int hash32(final String data) {  
        byte[] bytes = toBytesWithoutEncoding(data);  
        return hash32(bytes, bytes.length, 0x9747b28c);  
    }  
  
    /** 
     *  * Generates 64 bit hash from byte array of the given length and seed.  * 
     *  * @param data byte array to hash  * @param length length of the array to 
     * hash  * @param seed initial seed value  * @return 64 bit hash of the 
     * given array   
     */  
    public long hash64(final byte[] data, int length, int seed) {  
        final long m = 0xc6a4a7935bd1e995L;  
        final int r = 47;  
        long h = (seed & 0xffffffffl) ^ (length * m);  
        int length8 = length / 8;  
        for (int i = 0; i < length8; i++) {  
            final int i8 = i * 8;  
            long k = ((long) data[i8 + 0] & 0xff)  
                    + (((long) data[i8 + 1] & 0xff) << 8)  
                    + (((long) data[i8 + 2] & 0xff) << 16)  
                    + (((long) data[i8 + 3] & 0xff) << 24)  
                    + (((long) data[i8 + 4] & 0xff) << 32)  
                    + (((long) data[i8 + 5] & 0xff) << 40)  
                    + (((long) data[i8 + 6] & 0xff) << 48)  
                    + (((long) data[i8 + 7] & 0xff) << 56);  
            k *= m;  
            k ^= k >>> r;  
            k *= m;  
            h ^= k;  
            h *= m;  
        }  
        switch (length % 8) {  
        case 7:  
            h ^= (long) (data[(length & ~7) + 6] & 0xff) << 48;  
        case 6:  
            h ^= (long) (data[(length & ~7) + 5] & 0xff) << 40;  
        case 5:  
            h ^= (long) (data[(length & ~7) + 4] & 0xff) << 32;  
        case 4:  
            h ^= (long) (data[(length & ~7) + 3] & 0xff) << 24;  
        case 3:  
            h ^= (long) (data[(length & ~7) + 2] & 0xff) << 16;  
        case 2:  
            h ^= (long) (data[(length & ~7) + 1] & 0xff) << 8;  
        case 1:  
            h ^= (long) (data[length & ~7] & 0xff);  
            h *= m;  
        }  
        ;  
        h ^= h >>> r;  
        h *= m;  
        h ^= h >>> r;  
        return h;  
    }  
  
    /** 
     *  * Generates 64 bit hash from byte array with default seed value.  *  * @param 
     * data byte array to hash  * @param length length of the array to hash  * @return 
     * 64 bit hash of the given string   
     */  
    public long hash64(final byte[] data, int length) {  
        return hash64(data, length, 0xe17a1465);  
    }  
      
      
    public long hash64(final String data) {  
        byte[] bytes = toBytesWithoutEncoding(data);  
        return hash64(bytes, bytes.length);  
    }  
}