码迷,mamicode.com
首页 > 编程语言 > 详细

一致性hash算法简介与代码实现

时间:2015-07-12 12:39:51      阅读:160      评论:0      收藏:0      [点我收藏+]

标签:

一.简介:

一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:

1、平衡性(Balance)

2、单调性(Monotonicity)

3、分散性(Spread)

4、负载(Load)

普通的哈希算法(也称硬哈希)采用简单取模的方式,将机器进行散列,这在cache环境不变的情况下能取得让人满意的结果,但是当cache环境动态变化时,这种静态取模的方式显然就不满足单调性的要求(当增加或减少一台机子时,几乎所有的存储内容都要被重新散列到别的缓冲区中)。

一致性哈希算法的基本实现原理是将机器节点和key值都按照一样的hash算法映射到一个0~2^32的圆环上。当有一个写入缓存的请求到来时,计算Key值k对应的哈希值Hash(k),如果该值正好对应之前某个机器节点的Hash值,则直接写入该机器节点,如果没有对应的机器节点,则顺时针查找下一个节点,进行写入,如果超过2^32还没找到对应节点,则从0开始查找(因为是环状结构)。

实现代码1:

首先有一个设备类,定义了机器名和ip:

技术分享
public class Cache
{
    public String name;
    public String ipAddress;
}
View Code

主要的实现:

技术分享
public class Shard<T> { 
    //hash 算法并不是保证绝对的平衡,如果 cache 较少的话,对象并不能被均匀的映射到 cache 上,
    //所以增加虚拟节点
    private TreeMap<Long, T> nodes;
    private List<T> shards; //节点碎片
    private final int NODE_NUM = 10; // 每个机器节点关联的虚拟节点个数

    public Shard(List<T> shards) {
        this.shards = shards;
        init();
    }

    private void init() { 
        nodes = new TreeMap<Long, T>();
        for (int i = 0; i < shards.size(); i++) 
        { // 遍历真实节点
            final T shardInfo = shards.get(i);
            
            for (int n = 0; n < NODE_NUM; n++)
            {
                // 真实节点关联虚拟节点,真实节点是VALUE;
                nodes.put((long) Hash("SHARD-" + i + "-NODE-" + n), shardInfo);
            }
            System.out.println(shardInfo);
        }
    }

    public T getShardInfo(String key) {
        SortedMap<Long, T> tail = nodes.tailMap((long) Hash(key)); 
        if (tail.size() == 0) {
            return nodes.get(nodes.firstKey());
        }
        //找到最近的虚拟节点
        return tail.get(tail.firstKey());
    }
    
    /**
     * 改进的32位FNV算法,高离散
     * 
     * @param string
     *            字符串
     * @return int值
     */
    public static int Hash(String str) 
    {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (byte b : str.getBytes())
            hash = (hash ^ b) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        return hash;
    }

}
View Code

测试:

技术分享
public class Test
{

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        List<Cache> myCaches=new ArrayList<Cache>();
        Cache cache1=new Cache();
        cache1.name="COMPUTER1";
        Cache cache2=new Cache();
        cache2.name="COMPUTER2";
        myCaches.add(cache1);
        myCaches.add(cache2);
        
        
        Shard<Cache> myShard=new Shard<Cache>(myCaches);
        
        Cache currentCache=myShard.getShardInfo("info1");
        System.out.println(currentCache.name);
        
//        for(int i=0;i<20;i++)
//        {
//            String object=getRandomString(20);//产生20位长度的随机字符串
//            Cache currentCache=myShard.getShardInfo(object);
//            System.out.println(currentCache.name);
//        }
        
        
    }
    
    public static String getRandomString(int length) { //length表示生成字符串的长度
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";   
        Random random = new Random();   
        StringBuffer sb = new StringBuffer();   
        for (int i = 0; i < length; i++) {   
            int number = random.nextInt(base.length());   
            sb.append(base.charAt(number));   
        }   
        return sb.toString();   
     }   

}
View Code

上述实现略为简单,32FNV算法的一种改进

FNV哈希算法是一种高离散性的哈希算法,特别适用于哈希非常相似的字符串,例如:URL,IP,主机名,文件名等。

以下服务使用了FNV:
1、calc
2、DNS
3、mdbm key/value查询函数
4、数据库索引hash
5、主流web查询/索引引擎
6、高性能email服务
7、基于消息ID查询函数
8、auti-spam反垃圾邮件过滤器
9、NFS实现(比如freebsd 4.3, linux NFS v4)
10、Cohesia MASS project 
11、Ada 95的spellchecker
12、开源x86汇编器:flatassembler user-defined symbol hashtree
13、PowerBASIC
14、PS2、XBOX上的文本资源
15、非加密图形文件指纹
16、FRET
17、Symbian DASM
18、VC++ 2005的hash_map实现
19、memcache中的libketama
20、 PHP 5.x 
21、twitter中用于改进cache碎片
22、BSD IDE project
23、deliantra game server
24、 Leprechaun
25、IPv6流标签

实现代码2:

HashFunction:

技术分享
package ha;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashFunction {
    private MessageDigest md5 = null;

    public long hash(String key) {
        if (md5 == null) {
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("no md5 algorythm found");
            }
        }

        md5.reset();
        md5.update(key.getBytes());
        byte[] bKey = md5.digest();
        long res = ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16) | ((long) (bKey[1] & 0xFF) << 8)
                | (long) (bKey[0] & 0xFF);
        return res & 0xffffffffL;
    }

}
View Code

主要实现:

技术分享
package ha;

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<Long, T> circle = new TreeMap<Long, T>();   // 用来存储虚拟节点hash值 到真实node的映射

    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));
        }
    }

    /**
     * 获得一个最近的顺时针节点
     * @param key 为给定键取Hash,取得顺时针方向上最近的一个虚拟节点对应的实际节点
     * @return
     */
    public T get(Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        long hash = hashFunction.hash((String) key);
        if (!circle.containsKey(hash)) {
            SortedMap<Long, T> tailMap = circle.tailMap(hash); ////返回此映射的部分视图,其键大于等于 hash
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }
    
    public long getSize() {
        return circle.size();
    }
    
}
View Code

测试:

技术分享
package ha;

import ha2.Cache;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class MainApp {

    public static void main(String[] args) {

        Set<String> nodes = new HashSet<String>();
        nodes.add("127.0.0.1");
        nodes.add("192.144.111");
        nodes.add("127.0.0.3");
        ConsistentHash<String> consistentHash = new ConsistentHash<String>(new HashFunction(), 160, nodes);

        consistentHash.add("127.0.0.4");
//        consistentHash.add("E");
//        consistentHash.add("F");
//        consistentHash.add("G");
//        consistentHash.add("H");
//        consistentHash.add("I");
//        consistentHash.add("J");
//        consistentHash.add("K");
//        consistentHash.add("L");
//        consistentHash.add("M");
//        consistentHash.add("N");
//        consistentHash.add("O");
//        consistentHash.add("P");
//        consistentHash.add("R");
//        consistentHash.add("S");
//        consistentHash.add("T");
//        consistentHash.add("U");
//        consistentHash.add("V");
//        consistentHash.add("W");
        System.out.println(consistentHash.getSize());  //640

        System.out.println(consistentHash.get("127.0.0.1#111123"));
        System.out.println(consistentHash.get("127.0.0.33#111123"));//ip=>tcp
        
        Random random = new Random();

        int s = random.nextInt(100)%(100-0+1) + 0;
        System.out.println(s);
        
        int t = random.nextInt()*100;//key
        System.out.println(consistentHash.get(t+"#"));
        
        for(int i=0;i<20;i++)
        {
            String object = getRandomString(20);//产生20位长度的随机字符串
            System.out.println(consistentHash.get(object));
        }
        
    }

    public static String getRandomString(int length) { //length表示生成字符串的长度
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";   
        Random random = new Random();   
        StringBuffer sb = new StringBuffer();   
        for (int i = 0; i < length; i++) {   
            int number = random.nextInt(base.length());   
            sb.append(base.charAt(number));   
        }   
        return sb.toString();   
     }  
    
}
View Code

参考:

https://blog.helong.info/blog/2015/03/13/jump_consistent_hash/

 

一致性hash算法简介与代码实现

标签:

原文地址:http://www.cnblogs.com/super-d2/p/4640715.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!