1. 程式人生 > >一致性hash演算法 java實現

一致性hash演算法 java實現

public class NoName {

    private static final Map<String,String> ipMap = new HashMap<String, String>();
    private static final List<ServerNode> serverList = new ArrayList<ServerNode>();


    //初始化環狀連結串列
    private void init(){
            synchronized (NoName.class){
                //自定義環狀節點 可以是spring的bean注入 一般為了雜湊,1:100 物理節點:虛擬節點 #1-#100 虛擬節點與物理節點的轉換方法省略
                ipMap.put("192.168.0.101#1","192.168.0.101");
                ipMap.put("192.168.0.101#2","192.168.0.101");
                ipMap.put("192.168.0.101#3","192.168.0.101");
                ipMap.put("192.168.0.101#4","192.168.0.101");

                ipMap.put("192.168.0.102#1","192.168.0.102");
                ipMap.put("192.168.0.102#2","192.168.0.102");
                ipMap.put("192.168.0.102#3","192.168.0.102");
                ipMap.put("192.168.0.102#4","192.168.0.102");

                ipMap.put("192.168.0.103#1","192.168.0.103");
                ipMap.put("192.168.0.103#2","192.168.0.103");
                ipMap.put("192.168.0.103#3","192.168.0.103");
                ipMap.put("192.168.0.103#4","192.168.0.103");

                ipMap.put("192.168.0.104#1","192.168.0.104");
                ipMap.put("192.168.0.104#2","192.168.0.104");
                ipMap.put("192.168.0.104#3","192.168.0.104");
                ipMap.put("192.168.0.104#4","192.168.0.104");
                //拷貝
                Set<String> ipSet = ipMap.keySet();
                for(String ip:ipSet){
                    StringBuffer ipSb = new StringBuffer(ip);
                    serverList.add(new ServerNode(ipSb.reverse().toString()));//反序可以使key值雜湊
                }
                //排序
                Collections.sort(serverList, new Comparator<ServerNode>() {
                    @Override
                    public int compare(ServerNode o1, ServerNode o2) {//排序
                        return  Math.abs(o1.getIp().hashCode()) - Math.abs(o2.getIp().hashCode()) ;
                    }
                });
                //環形連結串列
                int size = serverList.size();
                for(int i=0;i<size;i++){
                    ServerNode serverNode = serverList.get(i);
                    if(i==0){
                        serverNode.setPre(serverList.get(size-1));
                    }else{
                        serverNode.setPre(serverList.get(i-1));
                    }
                    if(i==size-1){
                        serverNode.setNext(serverList.get(0));
                    }else{
                        serverNode.setNext(serverList.get(i+1));
                    }
                }
            }
    }

    private void findInWhichServer(String key){
        System.out.println(Math.abs(key.hashCode()));
        ServerNode targetNode =  search(Math.abs(key.hashCode()),serverList.get(0));
        System.out.println(targetNode.getIp());
//        System.out.println(ipMap.get(new StringBuffer(targetNode.getIp()).reverse().toString()));
    }
    private ServerNode search(int keyHash,ServerNode self){
        ServerNode pre = self.getPre();
        int preHash = Math.abs(pre.getIp().hashCode());
        int selfHash =  Math.abs(self.getIp().hashCode());
        if(selfHash>preHash){
            if(keyHash <= selfHash && keyHash > preHash){
                return self;
            }
        }else  if (selfHash==preHash){//只有1個memcache伺服器
            return self;
        }else{//跨界
            if(keyHash > preHash || keyHash <= selfHash){
                return self;
            }
        }
        return search(keyHash,self.getNext());
    }

    private static void iteratorList(){
        for(ServerNode serverNode:serverList){
            System.out.println(serverNode.getIp()+"--"+Math.abs(serverNode.getIp().hashCode()));
        }
    }
    public static void main(String[] args) {
        NoName noName = new NoName();
        noName.init();
//        iteratorList();
        noName.findInWhichServer("192.168.0.104");
        noName.findInWhichServer("CacheKey");
        noName.findInWhichServer("findYou3");
        noName.findInWhichServer("123");
        noName.findInWhichServer("You");

    }
}
public class ServerNode {
    private String ip;

    private ServerNode pre;
    private ServerNode next;

    public ServerNode(String ip, int min, int max) {
        this.ip = ip;
    }

    public ServerNode(String ip) {
//        System.out.println(ip+":"+Math.abs(ip.hashCode()));
        this.ip = ip;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public ServerNode getPre() {
        return pre;
    }

    public void setPre(ServerNode pre) {
        this.pre = pre;
    }

    public ServerNode getNext() {
        return next;
    }

    public void setNext(ServerNode next) {
        this.next = next;
    }
}

[轉載]【演算法學習】Consistent Hash 一致性雜湊

 (2011-02-24 17:39:29)

轉載▼

標籤: 

轉載

【摘要】

      前段時間再看Thinking In LAMP Blog中的2010-12-05_大型網際網路應用架構設計(視訊,PPT在此).欣喜若狂的發現了Consistent Hash(一致性雜湊),在之前的分散式中只瞭解mod,現在就認證研究一下Consistent Hash。Consistent hashing 演算法早在 1997 年就在論文 Consistent hashing and random trees

 中被提出,目前在 cache 系統中應用越來越廣泛.

【主題】

  • Consistent Hash
  • Cache::Memcached的分散式方法
  • Consistent Hashing

【內容】

1. 為什麼需要Consistent Hash?

      在Memcached分佈中一定會有如下需求,比如你有 N 個 cache 伺服器(後面簡稱cache),那麼如何將一個物件 object 對映到 N 個 cache 上呢,你很可能會採用類似下面的通用方法計算 object 的 hash 值,然後均勻的對映到到 N 個 cache ;

hash(object)%N

      一切都執行正常,再考慮如下的兩種情況增加和刪除;

      1. 一個 cache 伺服器 m down 掉了(在實際應用中必須要考慮這種情況),這樣所有對映到 cache m 的物件都會失效,怎麼辦,需要把 cache m 從 cache 中移除,這時候 cache 是 N-1 臺,對映公式變成了 

hash(object)%(N-1)

      2. 由於訪問加重,需要新增 cache ,這時候 cache 是 N+1 臺,對映公式變成了 

hash(object)%(N+1)

      1 和 2 意味著什麼?這意味著突然之間幾乎所有的 cache 都失效了。對於伺服器而言,這是一場災難,洪水般的訪問都會直接衝向後臺伺服器;

      再來考慮第三個問題,由於硬體能力越來越強,你可能想讓後面新增的節點多做點活,顯然上面的 hash 演算法也做不到。

      有什麼方法可以改變這個狀況呢,這就是 consistent hashing...

2. Hash演算法和單調性

      Hash 演算法的一個衡量指標是單調性( Monotonicity ),定義如下:

      單調性是指如果已經有一些內容通過雜湊分派到了相應的緩衝中,又有新的緩衝加入到系統中。雜湊的結果應能夠保證原有已分配的內容可以被對映到新的緩衝中去,而不會被對映到舊的緩衝集合中的其他緩衝區。

      容易看到,上面的簡單 hash 演算法 hash(object)%N 難以滿足單調性要求。

3. Consistent Hash 演算法的原理

      consistent hashing 是一種 hash 演算法,簡單的說,在移除 / 新增一個 cache 時,它能夠儘可能小的改變已存在 key 對映關係,儘可能的滿足單調性的要求。

      下面就來按照 5 個步驟簡單講講 consistent hashing 演算法的基本原理。

3.1 環形Hash空間

      考慮通常的 hash 演算法都是將 value 對映到一個 32 為的 key 值,也即是 0~2^32-1 次方的數值空間;我們可以將這個空間想象成一個首( 0)尾( 2^32-1 )相接的圓環,如下面圖 1 所示的那樣。

--圖1 環形 hash 空間

3.2 把物件對映到hash空間

      接下來考慮 4 個物件 object1~object4 ,通過 hash 函式計算出的 hash 值 key 在環上的分佈如圖 2 所示。

hash(object1) = key1;
… …
hash(object4) = key4;

-- 圖2 4個物件的 key 值分佈

3.3 把Cache對映到Hash空間

      Consistent hashing 的基本思想就是將物件和 cache 都對映到同一個 hash 數值空間中,並且使用相同的 hash 演算法。

      假設當前有 A,B 和 C 共 3 臺 cache ,那麼其對映結果將如圖 3 所示,他們在 hash 空間中,以對應的 hash 值排列。

hash(cache A) = key A;
… …
hash(cache C) = key C;

--圖3 cache 和物件的 key 值分佈

      說到這裡,順便提一下 cache 的 hash 計算,一般的方法可以使用 cache 機器的 IP 地址或者機器名作為 hash 輸入。

3.4 把物件對映到cache

      現在 cache 和物件都已經通過同一個 hash 演算法對映到 hash 數值空間中了,接下來要考慮的就是如何將物件對映到 cache 上面了。

      在這個環形空間中,如果沿著順時針方向從物件的 key 值出發,直到遇見一個 cache ,那麼就將該物件儲存在這個 cache 上,因為物件和 cache 的 hash 值是固定的,因此這個 cache 必然是唯一和確定的。這樣不就找到了物件和 cache 的對映方法了嗎?!

      依然繼續上面的例子(參見圖 3 ),那麼根據上面的方法,物件 object1 將被儲存到 cache A 上; object2 和 object3 對應到 cache C ; object4 對應到 cache B;

3.5 考察Cache的變動

      前面講過,通過 hash 然後求餘的方法帶來的最大問題就在於不能滿足單調性,當 cache 有所變動時, cache 會失效,進而對後臺伺服器造成巨大的衝擊,現在就來分析分析 consistent hashing 演算法。

3.5.1 移除 cache

      考慮假設 cache B 掛掉了,根據上面講到的對映方法,這時受影響的將僅是那些沿 cache B 逆時針遍歷直到下一個 cache ( cache C )之間的物件,也即是本來對映到 cache B 上的那些物件。

      因此這裡僅需要變動物件 object4 ,將其重新對映到 cache C 上即可;參見圖 4 。

--圖4 Cache B 被移除後的 cache 對映

有興趣可以瞭解下這款國內人氣很旺的JAVA程式碼生成器基於拖拽,不用寫複雜的模板,支援多種資料庫,適配wap,管理後臺各種功能全有 免費開源 地址:https://blog.csdn.net/adyuebanwan/article/details/83006405 或者 http://www.magicalcoder.com

=======================================================================================