這幾天看了幾遍一致性哈希的文章,但是都沒有比較完整的實現(xiàn),因此試著實現(xiàn)了一下,這里我就不講一致性哈希的原理了,網(wǎng)上很多,以一致性哈希用在負載均衡的實例來說,一致性哈希就是先把主機ip從小大到全部放到一個環(huán)內(nèi),然后客戶端ip來連接的時候,把客戶端ip連接到大小最接近客戶端ip且大于客戶端ip的主機。當(dāng)然,這里的ip一般都是要先hash一下的。我的程序運行結(jié)果如下:
- 添加客戶端,一開始有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
看結(jié)果可知:一開始添加到9個客戶端,連接到主機s1,s2,s3,s4的客戶端分別有3,3,3,0個,經(jīng)過刪除主機s2,添加主機s5,最后9個客戶端分別連接到主機s1,s2,s3,s4,s5的個數(shù)為4,0,1,2,2.這里要說明一下刪除主機s2的情況,hash尾號為9995的客戶端先連接到s2,再連接到s1,為什么會出現(xiàn)這種情況呢?因為每一個真實主機有n個虛擬主機,刪除s2卻打印“hash(7274050343425899995)改變到->s2-192.168.1.2”是因為刪除了s2的其中一個虛擬主機,跳轉(zhuǎn)到另一個虛擬主機,但還是在s2上,當(dāng)然,這里是打印中間情況,以便了解,真實的環(huán)境是刪除了s2后,所有他的虛擬節(jié)點都會馬上被刪除,虛擬節(jié)點上的連接也會重新連接到另一個主機的虛擬節(jié)點,不會存在這種中間情況。
以下給出所有的實現(xiàn)代碼,大家共同學(xué)習(xí):
- public class Shard<Node> { // S類封裝了機器節(jié)點的信息 ,如name、password、ip、port等
-
- static private TreeMap<Long, Node> nodes; // 虛擬節(jié)點到真實節(jié)點的映射
- static private TreeMap<Long,Node> treeKey; //key到真實節(jié)點的映射
- static private List<Node> shards = new ArrayList<Node>(); // 真實機器節(jié)點
- private final int NODE_NUM = 100; // 每個機器節(jié)點關(guān)聯(lián)的虛擬節(jié)點個數(shù)
- 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環(huán)
- nodes = new TreeMap<Long, Node>();
- treeKey = new TreeMap<Long, Node>();
- for (int i = 0; i != shards.size(); ++i) { // 每個真實機器節(jié)點都需要關(guān)聯(lián)虛擬節(jié)點
- final Node shardInfo = shards.get(i);
-
- for (int n = 0; n < NODE_NUM; n++)
- // 一個真實機器節(jié)點關(guān)聯(lián)NODE_NUM個虛擬節(jié)點
- 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);
-
- }
-
- //添加一個虛擬節(jié)點進環(huán)形結(jié)構(gòu),lg為虛擬節(jié)點的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()));
- }
- }
- }
-
- //刪除真實節(jié)點是s
- public void deleteS(Node s){
- if(s==null){
- return;
- }
- System.out.println("刪除主機"+s+"的變化:");
- for(int i=0;i<NODE_NUM;i++){
- //定位s節(jié)點的第i的虛擬節(jié)點的位置
- 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節(jié)點的第i個虛擬節(jié)點
- flag = true;
- }else{
- begin = head.lastKey();
- end = tail.firstKey();
- tail.remove(tail.firstKey());
- between = treeKey.subMap(begin, end);//在s節(jié)點的第i個虛擬節(jié)點的所有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到真實節(jié)點
- public void keyToNode(String key){
- SortedMap<Long, Node> tail = nodes.tailMap(hash(key)); // 沿環(huán)的順時針找到一個虛擬節(jié)點
- 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算法,性能很高,
- * 比傳統(tǒng)的CRC32,MD5,SHA-1(這兩個算法都是加密HASH算法,復(fù)雜度本身就很高,帶來的性能上的損害也不可避免)
- * 等HASH算法要快很多,而且據(jù)說這個算法的碰撞率很低.
- * 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
版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
posted on 2015-10-19 15:23
fly 閱讀(217)
評論(0) 編輯 收藏 所屬分類:
J2EE-負載均衡