第一節 Memcached分佈式緩存入門

關於Memcached的博文太多了,以下是個人學習的收集整理。

本節討論問題:

  • 簡單介紹與應用
  • 下載安裝注意事項
  • 簡單測試
  • Memcached分佈式原理

一、介紹與應用

     在常規的WEB開發下,基本都會利用到緩存用以降低對數據庫的壓力,提高訪問速度。有時候緩存的數據多了,並且其它站點也想獲取這些緩存數據時就出現在了問題。通常IIS站點都是以應用程序池劃分管理,同一個池下又可劃分多個應用程序域,不管是不同的應用程序域或是不同應用程序池,其之間的緩存都是無法相互訪問的。因此很多站點就會重複建立相同的緩存,以便訪問。但是,一旦一個站點的緩存被更新了,又如何通知其它站點更新呢。我記得Discuz.net中做法,是通過監控配置文件的修改來實現的,其原理就是一個站點緩存更新了,就去修改對應的配置文件中的項。其它站點監控到配置文件被修改,就去檢查哪一項目被改了,然後重新加載緩存。是不是不太靈活?如果緩存的數據要分佈到其它服務器上,以降低對同一臺服務器的壓力,如何實現呢?緩存服務器又如何實現擴展呢?這便是我們這篇Memcached引入的原因。有做即時通訊,遊戲大廳的還可以採用一下shuttler.net

關於Memcached以下爲摘自博文http://www.cnblogs.com/zjneter/archive/2007/07/19/822780.html

Memcached是什麼?
      Memcached是由Danga Interactive開發的,高性能的,分佈式的內存對象緩存系統,用於在動態應用中減少數據庫負載,提升訪問速度。
Memcached能緩存什麼?
     通過在內存裏維護一個統一的巨大的hash表,Memcached能夠用來存儲各種格式的數據,包括圖像、視頻、文件以及數據庫檢索的結果等。
Memcached快麼?
     非常快。Memcached使用了libevent(如果可以的話,在linux下使用epoll)來均衡任何數量的打開鏈接,使用非阻塞的網絡I/O,對內部對象實現引用計數(因此,針對多樣的客戶端,對象可以處在多樣的狀態), 使用自己的頁塊分配器和哈希表, 因此虛擬內存不會產生碎片並且虛擬內存分配的時間複雜度可以保證爲O(1).。
Memcached的特點?
     Memcached的緩存是一種分佈式的,可以讓不同主機上的多個用戶同時訪問, 因此解決了共享內存只能單機應用的侷限,更不會出現使用數據庫做類似事情的時候,磁盤開銷和阻塞的發生。

二、下載與安裝

服務端與For .net開發下載 可以參照這篇博文http://blog.csdn.net/cnkiminzhuhu/archive/2009/10/28/4739859.aspx
客戶端的版本比較多,並且不能互用,因爲採用了壓縮機制,日誌等功能,所以在選擇客戶端時要注意這些。

服務端源碼  下載

a.windows下 直接使用memcached.exe 程序就可以了,也可以將此程序安裝爲windows服務。安裝爲windows服務後要通過telnet命令來操作服務端

命令行輸入 'c:\memcached\memcached.exe -d install' 
命令行輸入 'c:\memcached\memcached.exe -d start' ,該命令啓動 Memcached ,默認監聽端口爲 11211

b.安裝爲單一服務不方便管理,這裏有藉助於memcacheddotnet_clientlib開發的一款服務端管理工具

服務端管理工具 下載  解壓安裝後工具裏的memcached.exe比較老,可直接用最新的替換掉

我們來看一下服務端工具安裝後文件結構

運行服務端管理工具,創建memcached服務端,以下爲演示步驟

1 服務端配置

2 添加Memcached服務

3 狀態觀察

4.查看Generate配置信息,提供給客戶端配置文件使用.

藉助服務端管理工具可以方便的觀察,或者你也可以採用telnet方式訪問查看了。這樣服務端工作就進行了完了,接下來就是要選擇一款合的客戶端開發了

Windows / .NET

a. .Net memcached client    1.1.5版本測試一下。
    https://sourceforge.net/projects/memcacheddotnet  
b. .Net 2.0 memcached client 這款應用比較廣泛,不過好長時間沒有更新了,最後一次更新是在2009.10 (網上其它鏈接提供的下載版本太老了)
    http://www.codeplex.com/EnyimMemcached 
    Client developed in .NET 2.0 keeping performance and extensibility in mind. (Supports consistent hashing.) 
    http://www.codeplex.com/memcachedproviders   (PDF 文檔)

   Current Release Memcached Providers 1.2(最後一版正式版)(1.2以後到1.4.4 for win32 應該都不是正式版了)
  - Walkthoughs on how to setup and use Memcached Cache Provider and Session State Provider is added to the Memcached Providers 1.2
   按照上面的意思Session State Provider is added 會話狀態保存的功能已經有了? 經過下載解壓後確認是有了這個功能,從1.2版本以後增加了SQL 腳本,即將Session保存到數據庫中了-_-|||。很多人擔心的Session問題終於有着落了。
c. BeIT Memcached Client (optimized C# 2.0)   這款最後一次更新時間是2010.8.4
    http://code.google.com/p/beitmemcached  
d. jehiah 
    http://jehiah.cz/projects/memcached-win32 



經過一番比較,我還是比較看中a,b,c三款,分別下載下來測試一下吧。(鑑於服務端管理工具匹配還是推薦enyim比較好)

三、簡單測試

1.memcacheddonet client測試 1.1.5版本 源碼有SRC包

[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class MemcachedBench   
  2.     {  
  3.         /// <summary>  
  4.         /// Arguments:   
  5.         ///        arg[0] = the number of runs to do  
  6.         ///        arg[1] = the run at which to start benchmarking  
  7.         /// </summary>  
  8.         /// <param name="args"></param>  
  9.         [STAThread]  
  10.         public static void Main(String[] args)   
  11.         {  
  12.             int runs = 100;  
  13.             int start = 200;  
  14.             if(args.Length > 1)  
  15.             {  
  16.                 runs = int.Parse(args[0]);  
  17.                 start = int.Parse(args[1]);  
  18.             }  
  19.   
  20.             //設置服務器列表  
  21.             string[] serverlist = { "172.16.76.98:11211""172.16.0.21:11211""172.16.125.76:11211""172.16.125.76:11212" };  
  22.   
  23.             // initialize the pool for memcache servers 創建連接池  
  24.             SockIOPool pool = SockIOPool.GetInstance();  
  25.             //設置服務器列表  
  26.             pool.SetServers(serverlist);  
  27.             //初始化  
  28.             pool.InitConnections = 3;  
  29.             pool.MinConnections = 3;  
  30.             pool.MaxConnections = 5;  
  31.             pool.SocketConnectTimeout = 1000;  
  32.             pool.SocketTimeout = 3000;  
  33.             pool.MaintenanceSleep = 30;  
  34.             pool.Failover = true;  
  35.             pool.Nagle = false;  
  36.             pool.Initialize();  
  37.               
  38.             // initialize the pool for memcache servers 全屬性注入  
  39. //            SockIOPool pool = SockIOPool.Instance;  
  40. //            pool.Servers = serverlist;  //屬性方式配置//  
  41. //            pool.InitConn = 5;  
  42. //            pool.MinConn = 5;  
  43. //            pool.MaxConn = 50;  
  44. //            pool.MaintSleep = 30;  
  45. //            pool.SocketTO = 1000;//  
  46. //            pool.Nagle = false;  
  47. //            pool.Initialize();  
  48.   
  49. //            // get client instance  
  50.             MemcachedClient mc = new MemcachedClient();  
  51.             mc.EnableCompression = false;  //是否啓用壓縮(通過ICSharpCode.SharpZipLib對存儲的數據壓縮)  
  52.             
  53. //            MemcachedClient mc = new MemcachedClient();  
  54. //            mc.CompressEnable = false;  
  55. //            mc.CompressThreshold = 0;  
  56. //            mc.Serialize = true;    //新的類中已沒有此屬性了,是默認幫你序列了?還是要自己實現呢?             
  57.   
  58.             string keyBase = "testKey";  
  59.             string obj = "This is a test of an object blah blah es, serialization does not seem to slow things down so much.  The gzip compression is horrible horrible performance, so we only use it for very large objects.  I have not done any heavy benchmarking recently";  
  60.   
  61.             //循環記時往服務器緩存上插入數據  等會我們要觀察一下數據都存到哪個服務器上的Memcached server上了  
  62.             long begin = DateTime.Now.Ticks;  
  63.             for(int i = start; i < start+runs; i++)   
  64.             {  
  65.                 mc.Set(keyBase + i, obj);  
  66.             }  
  67.             long end = DateTime.Now.Ticks;  
  68.             long time = end - begin;  
  69.   
  70.             //計算存儲這些數據花了多長時間  
  71.             Console.WriteLine(runs + " sets: " + new TimeSpan(time).ToString() + "ms");  
  72.   
  73.             //開始取數據,並記時  
  74.             begin = DateTime.Now.Ticks;  
  75.             int hits = 0;  
  76.             int misses = 0;  
  77.             for(int i = start; i < start+runs; i++)   
  78.             {  
  79.                 string str = (string) mc.Get(keyBase + i);  
  80.                 if(str != null)  
  81.                     ++hits;    //成功取到數據  
  82.                 else  
  83.                     ++misses;  //丟失次數  
  84.             }  
  85.             end = DateTime.Now.Ticks;  
  86.             time = end - begin;  
  87.   
  88.             //獲取這些數據花了多長時間  
  89.             Console.WriteLine(runs + " gets: " + new TimeSpan(time).ToString() + "ms");  
  90.             Console.WriteLine("Cache hits: " + hits.ToString());  
  91.             Console.WriteLine("Cache misses: " + misses.ToString());  
  92.             Console.WriteLine("--------------------------------------------------------\r\n");  
  93.   
  94.             Console.WriteLine("各服務器狀態:");  
  95.             Console.WriteLine("-------------------------------------------------------");  
  96.             IDictionary stats = mc.Stats();  
  97.             foreach(string key1 in stats.Keys)  
  98.             {  
  99.                 Console.WriteLine(key1);  
  100.                 Hashtable values = (Hashtable)stats[key1];  
  101.                 foreach(string key2 in values.Keys)  
  102.                 {  
  103.                     Console.WriteLine(key2 + ":" + values[key2]);  
  104.                 }  
  105.                 Console.WriteLine("-------------------------------------------------------");  
  106.             }  
  107.             //從這裏可以看出SockIOPool應該建立了一張HashTable去管理所有連接池實例  
  108.             SockIOPool.GetInstance().Shutdown();  
  109.   
  110.             Console.ReadLine();  
  111.         }  


 

分別設置了3臺,4臺,5臺服務器測試了3次,測試結果如下:

100條數據都確實存上去成功了,但是取數據命中率會隨着服務器增多急劇下降!才幾臺測試服務器,結果就如此差!問題出在哪裏了呢? 服務器的memcached.exe太老了? 還是看一下memcached的實現原理了

這裏有我之前下載的一版memcached的原理介紹 下載

裏面提到查找服務器端數據的算法 是求餘算法 ,而這中算法的命中率很差,並且隨便服務器節點的變動(增加或刪除節點)命中率急劇下降。

難道是這個原因? 瞭解到memcached的客戶端算法已經修改爲Consistent Hashing算法,難道是我下載的客戶端版本確實很老了? 順便說一下這個Consistent Hashing算法是將服務器按環形分佈在一個圓上,按我個人理解,服務器數量越多,環形分佈越相對穩定,這個時候增刪服務器對定位的影響都比較小。在之後兩個客戶端的版本再測試一次看看結果,這節就到這裏吧。附一張用MemcacheD Manager監控圖

Memcached分佈式原理

由於測試讀取數據的命中率太差,去查閱了一下Memcached的分佈式原理。

先看一下應用場景

第一次訪問先從數據庫中得到數據並保存到緩存中,第二次再讀數據就從緩存中獲取,這是正常情情,當第二次沒有命中數據?這個時候你是否回數據庫中讀取數據呢?讀取數據後,你是否還要保存到緩存中呢,但這個數據緩存中又是存在的,如何處理呢? 這就是Memcached客戶端沒有命中數據導致的後果。

我們來看一下Memcached的原理:

memcached雖然稱爲“分佈式”緩存服務器,但服務器端並沒有“分佈式”功能,而是完全由客戶端程序庫實現的。服務端之間沒有任何聯繫,數據存取都是通過客戶端的算法實現的。客戶端初始化的獲取所有服務器的哈希列表,當需要存取數據就會檢索這個哈希列表查找到對應的服務器。看下圖

當客戶端要存取數據時,首先會通過算法查找自己維護到的服務器哈希列表,找到對應的服務器後,再將數據存往指定服務器。這裏關鍵點是使用了什麼算法!

這個問題也不追查,接着往下看,我來再去取原先這個數據

查找數據的原理和存取的原理是一樣,首先通過算法在維護的哈希列表查到對應服務器,然後再去指定服務器讀取數據。那問題來了,他是如何準確的找到這臺服務器的呢?算法,算法就是他的原因,只要你在存和取的時候使用的算法是一樣的,那算法計算的結果也是一致的,所以就可以正確的找到服務器了。

那爲什麼我們在第三節的測試中,命中率會如此之差呢?

我們使用的Memcachedonet client 老版本使用的是求餘算法,我們來看看這個求餘算法的定義--“根據服務器臺數的餘數進行分散”。即求得鍵的整
數哈希值,再除以服務器臺數,根據其餘數來選擇服務器。我們在存數據的時候,計算出這個數據鍵的CRC值,用這個值除以服務器臺數求得餘數來存往指定的服務器。那反過來取數據依然是這個算法,那結果肯定是一致的。問題是,爲什麼不同臺數的服務器測試中,命中率會變化這麼大呢。餘數計算的方法簡單,數據的分散性也相當優秀,但也有其缺點。那就是當添加或移除服務器時,緩存重組的代價相當巨大。添加服務器後,餘數就會產生鉅變,這樣就無法獲取與保存時相同的服
務器,從而影響緩存的命中率。因爲增刪服務器後,數據鍵的CRC值是不變的,但是服務器的臺數變了,導致求餘的結果也發生變化了,從而影響了命中率。

我們再來看一下改進的Consistent Hashing算法,可以確定是的,Memcachedonet的版本是沒有采用這個算法。

Consistent Hashing如下所示:首先求出memcached服務器(節點)的哈希值,並將其配置到0~2的32次方的圓(continuum)上。然後用同樣的方法求出存儲數據的鍵的哈希值,並映射到圓上。然後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。如果超過2的32次方仍然找不到服務器,就會保存到第一臺memcached服務器上。

當從上圖的狀態中添加一臺memcached服務器。餘數分佈式算法由於保存鍵的服務器會發生巨大變化而影響緩存的命中率,但Consistent Hashing中,只有在continuum上增加服務器的地點逆時針方向的第一臺服務器上的鍵會受到影響。如下圖

從上圖可以看到,添加新的節點5時並不會重新分佈所有節點,而是在之前的基礎上某個位置插入新的節點,這樣保證了整體的分佈沒有發生太大變化,並且順時針方向的沒有影響,逆時針方向的從第一臺就開始有影響了。因此,Consistent Hashing最大限度地抑制了鍵的重新分佈。但是這樣的誤差還是有的,因爲根據服務器的哈希值來分佈本身就是不均勻的。後面有提到改進的Consistent Hashing算法,即在圓環上預先分佈爲每臺服務器分佈一定數量的虛定擬節點,相當於我們均勻分佈了圓環上的節點,當有節點增加或刪除時都是在指定的位置上進行的就抑制了分佈不均勻,最大限度地減小服務器增減時的緩存重新分。使用Consistent Hashing算法的memcached客戶端函數庫進行測試的結果是,由服務器臺數(n)和增加的服務器臺數(m)計算增加服務器後的命中率計算公式如下:(1 n/(n+m)) * 100

(參考之前提供下載的PDF原理一文)

     memcachedonet 使用的是餘數算法,可能是導致誤差的原因。在下一節中,我們來學習一下enyim.client(memcachedproviders),並進行相關測試。

原文:http://blog.csdn.net/xingxing513234072/article/details/39345585

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