分佈式設計《初嘗memcached》

      之前聽說過高性能的分佈式緩存開源工具,但一直沒有真正接觸過,現在接觸的產品中有用到過分佈式緩存,所以決定一探究竟。memcached是一個優秀的開源的分佈式緩存工具,也是目前比較火熱的分佈式緩存的解決方案雛形。memcached的服務端產品本身功能簡潔,簡單易用,但是玩法多種多樣。但是事實上它是一個“僞分佈式”解決方案,它本身並沒有實現服務端分佈式(服務端的memcached server之間是不能通信的),所謂的分佈式都是依靠客戶端來實現,而目前市面上提供了客戶端分佈式實現的開源工具很多,在這裏我主要以Spymemcached這個客戶端實現爲基礎講述一些memcached的原理和應用。

【原理說明】
    之前提到了,memcached產品本身並未實現分佈式,所以藉助下列兩幅網上流行的圖片便可以直觀的瞭解memcached的原理以及怎麼玩分佈式的。

   1、存儲(set)
假設memcached server有node1/node2/node3三個節點,現在應用程序需要存儲"tokyo"/"test"這樣一對鍵值對。memcached客戶端接收應用程序傳來的鍵值對"tokyo"/"test",通過算法(文章尾部會介紹具體的算法細節)從服務器列表中選中了node1作爲目標存儲服務器,接着發送set指令命令node1執行存儲任務。
圖1 存儲數據

    2、獲取(get)
假設應用程序現在需要獲取鍵"tokyo"對應的值數據"test",memcached客戶端程序接收參數"tokyo",通過同樣的算法從服務器列表中選中node1,接着發送get指令命令node1獲取鍵"tokyo"對應的值數據。

                                                      圖2  獲取數據

【使用場景】
    在網上也看過一些前輩描述過一些關於使用場景的描述,我簡單總結下大致就以下兩點
    1、從memcached設計初衷的角度來看,memcached可以減少網站數據庫的開銷。對於經常需要讀取,而又不經常改變的數據完全可以放到memcached中。
    2、分佈式應用之間共享數據。舉個例子,登陸系統和商品查詢系統是獨立部署,並且是集羣的。用戶登陸了登陸系統之後如何將登錄信息與其他的業務系統(商品查詢系統)共享信息,這時就可以使用memcached緩存登錄信息,商品查詢系統便可以從memcached中獲取用戶的登陸信息。

【客戶端源碼分析】

    1、客戶端調用
    以Spymemcached客戶端實現爲例,下面貼一段客戶端的簡單應用代碼。下載鏈接memcached client
package com.lvmama.memcached;
import java.net.InetSocketAddress;
import net.spy.memcached.MemcachedClient;
public class TestSpymemcached {
 public static void main(String[] args) throws Exception{
  MemcachedClient client = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211)); //創建連接
  client.set("name", 10, "tony"); //set數據
  Object name = client.get("name"); //get數據
  System.out.println("name:" + name);
 }
}


   2、餘數hash算法
    實現類爲ArrayModNodeLocator,將傳入的參數k(鍵),通過hash算法得出一個整數值,計算下memcached server的個數。拿着參數k的hash值對服務器節點的個數求餘數。餘數便是選中的服務器節點。

  public MemcachedNode getPrimary(String k) {
    return nodes[getServerForKey(k)]; 
  }

  private int getServerForKey(String key) {
    int rv = (int) (hashAlg.hash(key) % nodes.length);
    assert rv >= 0 : "Returned negative key for key " + key;
    assert rv < nodes.length : "Invalid server number " + rv + " for key "
        + key;
    return rv;
  }

   3、consistent hash算法
    算法原理見下圖
    第一步:將memcached服務器節點的hash值映射到一個0~2的32次方的環形數據結構上,存儲方式爲k(hash值),v(服務器節點);
    第二步:將要保存的參數k計算hash值,並映射到環形數據結構中;
    第三步:從參數k的hash值順時針查找已映射的hash值,第一個hash值對應的服務器節點便是選中的目標存儲服務器。

                                                 圖 3   consistent hash算法

    實現類爲KetamaNodeLocator,代碼結構比較簡單:將要保存的k計算hash值作爲參數傳入getNodeForKey()方法,從hash值順時針查找到剩餘的環形數據結構tailMap,如果tailMap不爲空則取tailMap中第一個已經映射hash值,如果tailMap爲空則取整個環形數據結構ketamaNodes的第一個已經映射的hash值(從0開始),取得hash值便可以找到對應的memcached服務器節點。

  public MemcachedNode getPrimary(final String k) {
    MemcachedNode rv = getNodeForKey(hashAlg.hash(k));
    assert rv != null : "Found no node for key " + k;
    return rv;
  }

  MemcachedNode getNodeForKey(long hash) {
    final MemcachedNode rv;
    if (!ketamaNodes.containsKey(hash)) {
      // Java 1.6 adds a ceilingKey method, but I'm still stuck in 1.5
      // in a lot of places, so I'm doing this myself.
      SortedMap<Long, MemcachedNode> tailMap = getKetamaNodes().tailMap(hash);
      if (tailMap.isEmpty()) {
        hash = getKetamaNodes().firstKey();
      } else {
        hash = tailMap.firstKey();
      }
    }
    rv = getKetamaNodes().get(hash);
    return rv;
  }

4、算法優劣比較
餘數hash算法:當服務器節點存在增加或者減少時,get數據時求得的餘數同set數據時求得的餘數很可能就不是同一個值,這時便大大降低了緩存讀取的命中率。
consistent hash算法當服務器節點存在增加或者減少時,如圖3增加了node5,只有node2~node5之間的hash值會受到影響,由原來存儲時中的node4變成獲取數據時命中的node5,其餘hash值都不會受到影響。所以命中率較高。
而且有些consistent hash算法的實現採用了虛擬節點的思想,使用一般的hash函數會使得服務器節點的映射分佈的不均勻,因此可以爲每個物理服務器節點分配100~200虛擬映射點,這樣便可最大限度的減少節點分佈不均的情況發生。


初嘗memcached,如有描述有誤,歡迎拍磚哈!後續會寫memcached服務端的內存模型,內存管理,源碼分析等文檔,歡迎大家一起探討!



















    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章