這幾天看了幾遍一致性哈希的文章,但是都沒有比較完整的實現,因此試著實現了一下,這里我就不講一致性哈希的原理了,網上很多,以一致性哈希用在負載均衡的實例來說,一致性哈希就是先把主機ip從小大到全部放到一個環內,然后客戶端ip來連接的時候,把客戶端ip連接到大小最接近客戶端ip且大于客戶端ip的主機。當然,這里的ip一般都是要先hash一下的。我的程序運行結果如下:
- 添加客戶端,一開始有4個主機,分別為s1,s2,s3,s4,每個主機有100個虛擬主機:
- 101客戶端(hash:-3872430075274208315)連接到主機->s2-192.168.1.2
- 102客戶端(hash:-6461488502093916753)連接到主機->s1-192.168.1.1
- 103客戶端(hash:-3272337528088901176)連接到主機->s3-192.168.1.3
- 104客戶端(hash:7274050343425899995)連接到主機->s2-192.168.1.2
- 105客戶端(hash:6218187750346216421)連接到主機->s1-192.168.1.1
- 106客戶端(hash:-8497989778066313989)連接到主機->s2-192.168.1.2
- 107客戶端(hash:2219601794372203979)連接到主機->s3-192.168.1.3
- 108客戶端(hash:1903054837754071260)連接到主機->s3-192.168.1.3
- 109客戶端(hash:-2425484502654523425)連接到主機->s1-192.168.1.1
- 刪除主機s2-192.168.1.2的變化:
- hash(-8497989778066313989)改變到->s4-192.168.1.4
- hash(7274050343425899995)改變到->s2-192.168.1.2
- hash(-3872430075274208315)改變到->s4-192.168.1.4
- hash(7274050343425899995)改變到->s1-192.168.1.1
- 增加主機s5-192.168.1.5的變化:
- hash(1903054837754071260)改變到->s5-192.168.1.5
- hash(1903054837754071260)改變到->s5-192.168.1.5
- hash(-3272337528088901176)改變到->s5-192.168.1.5
- 最后的客戶端到主機的映射為:
- hash(-8497989778066313989)連接到主機->s4-192.168.1.4
- hash(-6461488502093916753)連接到主機->s1-192.168.1.1
- hash(-3872430075274208315)連接到主機->s4-192.168.1.4
- hash(-3272337528088901176)連接到主機->s5-192.168.1.5
- hash(-2425484502654523425)連接到主機->s1-192.168.1.1
- hash(1903054837754071260)連接到主機->s5-192.168.1.5
- hash(2219601794372203979)連接到主機->s3-192.168.1.3
- hash(6218187750346216421)連接到主機->s1-192.168.1.1
- hash(7274050343425899995)連接到主機->s1-192.168.1.1
看結果可知:一開始添加到9個客戶端,連接到主機s1,s2,s3,s4的客戶端分別有3,3,3,0個,經過刪除主機s2,添加主機s5,最后9個客戶端分別連接到主機s1,s2,s3,s4,s5的個數為4,0,1,2,2.這里要說明一下刪除主機s2的情況,hash尾號為9995的客戶端先連接到s2,再連接到s1,為什么會出現這種情況呢?因為每一個真實主機有n個虛擬主機,刪除s2卻打印“hash(7274050343425899995)改變到->s2-192.168.1.2”是因為刪除了s2的其中一個虛擬主機,跳轉到另一個虛擬主機,但還是在s2上,當然,這里是打印中間情況,以便了解,真實的環境是刪除了s2后,所有他的虛擬節點都會馬上被刪除,虛擬節點上的連接也會重新連接到另一個主機的虛擬節點,不會存在這種中間情況。
以下給出所有的實現代碼,大家共同學習:
- public class Shard<Node> { // S類封裝了機器節點的信息 ,如name、password、ip、port等
-
- static private TreeMap<Long, Node> nodes; // 虛擬節點到真實節點的映射
- static private TreeMap<Long,Node> treeKey; //key到真實節點的映射
- static private List<Node> shards = new ArrayList<Node>(); // 真實機器節點
- private final int NODE_NUM = 100; // 每個機器節點關聯的虛擬節點個數
- boolean flag = false;
-
- public Shard(List<Node> shards) {
- super();
- this.shards = shards;
- init();
- }
-
- public static void main(String[] args) {
- // System.out.println(hash("w222o1d"));
- // System.out.println(Long.MIN_VALUE);
- // System.out.println(Long.MAX_VALUE);
- Node s1 = new Node("s1", "192.168.1.1");
- Node s2 = new Node("s2", "192.168.1.2");
- Node s3 = new Node("s3", "192.168.1.3");
- Node s4 = new Node("s4", "192.168.1.4");
- Node s5 = new Node("s5","192.168.1.5");
- shards.add(s1);
- shards.add(s2);
- shards.add(s3);
- shards.add(s4);
- Shard<Node> sh = new Shard<Shard.Node>(shards);
- System.out.println("添加客戶端,一開始有4個主機,分別為s1,s2,s3,s4,每個主機有100個虛擬主機:");
- sh.keyToNode("101客戶端");
- sh.keyToNode("102客戶端");
- sh.keyToNode("103客戶端");
- sh.keyToNode("104客戶端");
- sh.keyToNode("105客戶端");
- sh.keyToNode("106客戶端");
- sh.keyToNode("107客戶端");
- sh.keyToNode("108客戶端");
- sh.keyToNode("109客戶端");
-
- sh.deleteS(s2);
-
-
- sh.addS(s5);
-
- System.out.println("最后的客戶端到主機的映射為:");
- printKeyTree();
- }
- public static void printKeyTree(){
- for(Iterator<Long> it = treeKey.keySet().iterator();it.hasNext();){
- Long lo = it.next();
- System.out.println("hash("+lo+")連接到主機->"+treeKey.get(lo));
- }
-
- }
-
- private void init() { // 初始化一致性hash環
- nodes = new TreeMap<Long, Node>();
- treeKey = new TreeMap<Long, Node>();
- for (int i = 0; i != shards.size(); ++i) { // 每個真實機器節點都需要關聯虛擬節點
- final Node shardInfo = shards.get(i);
-
- for (int n = 0; n < NODE_NUM; n++)
- // 一個真實機器節點關聯NODE_NUM個虛擬節點
- nodes.put(hash("SHARD-" + shardInfo.name + "-NODE-" + n), shardInfo);
- }
- }
- //增加一個主機
- private void addS(Node s) {
- System.out.println("增加主機"+s+"的變化:");
- for (int n = 0; n < NODE_NUM; n++)
- addS(hash("SHARD-" + s.name + "-NODE-" + n), s);
-
- }
-
- //添加一個虛擬節點進環形結構,lg為虛擬節點的hash值
- public void addS(Long lg,Node s){
- SortedMap<Long, Node> tail = nodes.tailMap(lg);
- SortedMap<Long,Node> head = nodes.headMap(lg);
- Long begin = 0L;
- Long end = 0L;
- SortedMap<Long, Node> between;
- if(head.size()==0){
- between = treeKey.tailMap(nodes.lastKey());
- flag = true;
- }else{
- begin = head.lastKey();
- between = treeKey.subMap(begin, lg);
- flag = false;
- }
- nodes.put(lg, s);
- for(Iterator<Long> it=between.keySet().iterator();it.hasNext();){
- Long lo = it.next();
- if(flag){
- treeKey.put(lo, nodes.get(lg));
- System.out.println("hash("+lo+")改變到->"+tail.get(tail.firstKey()));
- }else{
- treeKey.put(lo, nodes.get(lg));
- System.out.println("hash("+lo+")改變到->"+tail.get(tail.firstKey()));
- }
- }
- }
-
- //刪除真實節點是s
- public void deleteS(Node s){
- if(s==null){
- return;
- }
- System.out.println("刪除主機"+s+"的變化:");
- for(int i=0;i<NODE_NUM;i++){
- //定位s節點的第i的虛擬節點的位置
- SortedMap<Long, Node> tail = nodes.tailMap(hash("SHARD-" + s.name + "-NODE-" + i));
- SortedMap<Long,Node> head = nodes.headMap(hash("SHARD-" + s.name + "-NODE-" + i));
- Long begin = 0L;
- Long end = 0L;
-
- SortedMap<Long, Node> between;
- if(head.size()==0){
- between = treeKey.tailMap(nodes.lastKey());
- end = tail.firstKey();
- tail.remove(tail.firstKey());
- nodes.remove(tail.firstKey());//從nodes中刪除s節點的第i個虛擬節點
- flag = true;
- }else{
- begin = head.lastKey();
- end = tail.firstKey();
- tail.remove(tail.firstKey());
- between = treeKey.subMap(begin, end);//在s節點的第i個虛擬節點的所有key的集合
- flag = false;
- }
- for(Iterator<Long> it = between.keySet().iterator();it.hasNext();){
- Long lo = it.next();
- if(flag){
- treeKey.put(lo, tail.get(tail.firstKey()));
- System.out.println("hash("+lo+")改變到->"+tail.get(tail.firstKey()));
- }else{
- treeKey.put(lo, tail.get(tail.firstKey()));
- System.out.println("hash("+lo+")改變到->"+tail.get(tail.firstKey()));
- }
- }
- }
-
- }
-
- //映射key到真實節點
- public void keyToNode(String key){
- SortedMap<Long, Node> tail = nodes.tailMap(hash(key)); // 沿環的順時針找到一個虛擬節點
- if (tail.size() == 0) {
- return;
- }
- treeKey.put(hash(key), tail.get(tail.firstKey()));
- System.out.println(key+"(hash:"+hash(key)+")連接到主機->"+tail.get(tail.firstKey()));
- }
-
- /**
- * MurMurHash算法,是非加密HASH算法,性能很高,
- * 比傳統的CRC32,MD5,SHA-1(這兩個算法都是加密HASH算法,復雜度本身就很高,帶來的性能上的損害也不可避免)
- * 等HASH算法要快很多,而且據說這個算法的碰撞率很低.
- * http://murmurhash.googlepages.com/
- */
- private static Long hash(String key) {
-
- ByteBuffer buf = ByteBuffer.wrap(key.getBytes());
- int seed = 0x1234ABCD;
-
- ByteOrder byteOrder = buf.order();
- buf.order(ByteOrder.LITTLE_ENDIAN);
-
- long m = 0xc6a4a7935bd1e995L;
- int r = 47;
-
- long h = seed ^ (buf.remaining() * m);
-
- long k;
- while (buf.remaining() >= 8) {
- k = buf.getLong();
-
- k *= m;
- k ^= k >>> r;
- k *= m;
-
- h ^= k;
- h *= m;
- }
-
- if (buf.remaining() > 0) {
- ByteBuffer finish = ByteBuffer.allocate(8).order(
- ByteOrder.LITTLE_ENDIAN);
- // for big-endian version, do this first:
- // finish.position(8-buf.remaining());
- finish.put(buf).rewind();
- h ^= finish.getLong();
- h *= m;
- }
-
- h ^= h >>> r;
- h *= m;
- h ^= h >>> r;
-
- buf.order(byteOrder);
- return h;
- }
-
- static class Node{
- String name;
- String ip;
- public Node(String name,String ip) {
- this.name = name;
- this.ip = ip;
- }
- @Override
- public String toString() {
- return this.name+"-"+this.ip;
- }
- }
-
- }
參考:http://blog.csdn.net/wuhuan_wp/article/details/7010071
http://blog.csdn.net/haitao111313/article/details/7537799
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
posted on 2015-10-19 15:23
fly 閱讀(217)
評論(0) 編輯 收藏 所屬分類:
J2EE-負載均衡