一.简介:
一致性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; }
主要的实现:
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; } }
测试:
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(); } }
上述实现略为简单,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; } }
主要实现:
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(); } }
测试:
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(); } }
参考:
https://blog.helong.info/blog/2015/03/13/jump_consistent_hash/