首先,不得不說,許多語言都實現了鏈接Memcached的客戶端,其中以Perl、PHP爲主。 僅僅memcached網站上列出的語言就有:Perl、PHP、Python、Ruby、C#、C/C++以及Lua等。html
那麼,咱們做爲.Net碼農,天然是使用C#。既然Memcached客戶端有.Net版,那咱們就去下載一個來試試。node
下載文件:http://pan.baidu.com/s/1w9Q8I算法
memcached clientlib項目地址:http://sourceforge.net/projects/memcacheddotnet/數據庫
解壓該包,裏面有1.1和2.0兩個版本的,這裏咱們使用2.0版本的。(在壓縮包中的目錄地址爲:\memcacheddotnet_clientlib-1.1.5\memcacheddotnet\trunk\clientlib\src\clientlib\bin\2.0\Release)編程
上面的這四個dll就是咱們須要引入項目中的程序集,有了他們,咱們就能夠和Memcached服務器進行通訊了,爽歪歪啊。數組
(1)首先,打開Windows Server 2003虛擬機,開啓Memcached服務;(非必要操做,若是您是在本機,則可跳過這一步,只需開啓Memcached服務便可)緩存
(2)①打開VS,新建一個C#的控制檯應用程序,取名爲:MemcachedClientDemo。服務器
②新建一個文件夾,取名爲Lib,而後將上面下載的客戶端程序集dll拷貝到這個文件夾中,並添加對這幾個dll的引用。網絡
(3)開始寫代碼,經過Memcached客戶端與服務器進行通訊,請參閱下面的代碼:數據結構
[STAThread] static void Main(string[] args) { // Memcached服務器列表 // 若是有多臺服務器,則以逗號分隔,例如:"192.168.80.10:11211","192.168.80.11:11211" string[] serverList = { "192.168.80.10:11211" }; // 初始化SocketIO池 string poolName = "MyPool"; SockIOPool sockIOPool = SockIOPool.GetInstance(poolName); // 添加服務器列表 sockIOPool.SetServers(serverList); // 設置鏈接池初始數目 sockIOPool.InitConnections = 3; // 設置鏈接池最小鏈接數目 sockIOPool.MinConnections = 3; // 設置鏈接池最大鏈接數目 sockIOPool.MaxConnections = 5; // 設置鏈接的套接字超時時間(單位:毫秒) sockIOPool.SocketConnectTimeout = 1000; // 設置套接字超時時間(單位:毫秒) sockIOPool.SocketTimeout = 3000; // 設置維護線程運行的睡眠時間:若是設置爲0,那麼維護線程將不會啓動 sockIOPool.MaintenanceSleep = 30; // 設置SockIO池的故障標誌 sockIOPool.Failover = true; // 是否用nagle算法啓動 sockIOPool.Nagle = false; // 正式初始化容器 sockIOPool.Initialize(); // 獲取Memcached客戶端實例 MemcachedClient memClient = new MemcachedClient(); // 指定客戶端訪問的SockIO池 memClient.PoolName = poolName; // 是否啓用壓縮數據:若是啓用了壓縮,數據壓縮長於門檻的數據將被儲存在壓縮的形式 memClient.EnableCompression = false; Console.WriteLine("----------------------------測試開始----------------------------"); // 01.簡單的添加與讀取操做 memClient.Set("test1", "edisonchou"); Console.WriteLine("test1:{0}", memClient.Get("test1")); // 02.先添加後修改再讀取操做 memClient.Set("test2", "jacky"); Console.WriteLine("test2:{0}", memClient.Get("test2")); memClient.Set("test2", "edwin"); Console.WriteLine("test2:{0}", memClient.Get("test2")); memClient.Replace("test2", "lousie"); Console.WriteLine("test2:{0}", memClient.Get("test2")); // 03.判斷Key值是否存在 if (memClient.KeyExists("test2")) { Console.WriteLine("Key:test2 is existed"); } // 04.刪除指定Key值的數據 memClient.Add("test3", "memcached"); Console.WriteLine("test3:{0}", memClient.Get("test3")); memClient.Delete("test3"); if (!memClient.KeyExists("test3")) { Console.WriteLine("Key:test3 is not existed"); } // 05.設置數據過時時間:5秒後過時 memClient.Add("test4", "expired", DateTime.Now.AddSeconds(5)); Console.WriteLine("test4:{0}", memClient.Get("test4")); Console.WriteLine("Please waiting the sleeping time"); System.Threading.Thread.Sleep(6000); if(!memClient.KeyExists("test4")) { Console.WriteLine("test4 is expired"); } Console.WriteLine("----------------------------測試完成----------------------------"); // 關閉SockIO池 sockIOPool.Shutdown(); Console.ReadKey(); }
這裏,咱們來細細分析下這段神奇的代碼:
①首先定義了一個string類型的數組來記錄Memcached服務器的IP與端口信息,這裏須要注意的是若是有多臺Memcached服務器,須要使用逗號分隔開,例如:"192.168.80.10:11211","192.168.80.11:11211","192.168.80.12:11211";
②SockIOPool是一個基於Socket(套接字)的鏈接池,換個方式理解:Memcached其實就是一個Socket的服務器端,它不停地接收Memcached客戶端發來的讀寫請求命令。這裏使用了SockIOPool.GetInstance("MyPool")來獲取一個名爲MyPool的鏈接池實例,看到GetInstance()這個靜態方法,咱們便知道這是採用了單例模式。後面咱們爲其配置了可訪問的Memcached服務器列表、鏈接數、套接字超時時間等配置,最後調用Initialize()方法正式地初始化鏈接池,等待後面客戶端的鏈接;
PS:神馬是Socket?咱們能夠經過一個生活中的場景來理解:假如你要打電話給一個朋友,拿起電話先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就創建起了鏈接,就能夠講話了。等到大家的交流結束,掛斷電話以結束這次交談。So,這裏的電話就是一個Socket,你打電話至關於申請了一個Socket,告訴了Socket你要打給誰(對方的電話號碼你事先知道)。而後,你和對方進行聊天通話,至關於在向Socket發送數據和從Socket接收數據。最後,通話結束後,一方掛掉電話則至關於關閉Socket,撤銷鏈接。
在計算機網絡的鏈接過程當中,客戶端Socket通常會記錄服務器主機的IP地址、端口號,而後向服務器端進行鏈接併發送和接受數據。而服務器端開啓一個監聽的服務,則是至關於使用Socket指定監聽的端口,而後等待客戶端的鏈接,客戶端鏈接後則產生一個會話。會話完成後,則關閉鏈接。
③建立一個新的MemcachedClient(Memcached客戶端)對象,並指定要鏈接的套接字鏈接池的名稱,設置是否啓用壓縮(這裏設置爲false)。這裏咱們瞭解一下爲何要設置是否起用壓縮: 在Memcached中,數據是以Key/Value對的形式進行存儲,Key的長度是有限制的,Memcached服務端內部限制Key爲250字符,這個長度絕對是夠用了,建議不要超過最大長度,儘可能控制在200個字符如下。其實,咱們最關心的仍是Value的限制長度,Value的限制大小爲1MB,那麼若是有時候超過了1MB怎麼辦呢?這時候也許就可使用壓縮了,使用壓縮後若是小於1Mb仍是能夠存儲到該Key中。但若是即便壓縮後仍是超過1Mb,那可能會拆分到多個Key中去了。
PS:Key不能有空格和控制字符。推薦使用較短的Key,能夠節省服務器內存和網絡帶寬。另外,最重要的一點是:Key不能重複!
④使用客戶端爲咱們提供的各類讀寫API方法進行讀寫測試,如Set、Get、Replace、Add能夠進行數據的添加和修改,而KeyExists則能夠判斷服務器中是否含有指定Key的數據,Delete則提供了刪除指定Key的接口。這裏,你們能夠經過看代碼就能夠理解,我就很少廢話了。你們能夠注意到有個數據過時時間的可選參數,當數據在服務器中存儲了必定時間後就會失效,這個參數至關有用。
(4)如今咱們經過調試,查看這段代碼的結果:
(1)在虛擬機中克隆已存在的Windows Server,並設置這兩臺服務器名稱爲:MemcacheServer1和MemcachedServer2,IP地址設置爲:192.168.80.10與192.168.80.11,測試兩臺虛擬機與宿主機是否可以互相Ping通,爲構建Memcached服務器集羣作一個最小化的準備;
(2)既然咱們有了兩臺Memcached服務器,那咱們得試試Memcached集羣啊,因爲Memcached的集羣是在客戶端實現,因此咱們只須要將服務器的IP地址和端口號加入服務器列表的string數組就能夠了。因而,咱們修改上面的代碼:
①首先新建一個App.config文件,新增一個AppSetting項以下:通常來講,服務器的地址信息都是寫在配置文件中的,爲了追求標準,咱們也寫在配置文件裏邊
②將serverList從新定義:使用配置文件裏邊的Value;這裏須要注意的是,要使用ConfigurationManager這個類,須要在引用中添加對System.Configuration這個dll的引用;
string[] serverList = ConfigurationManager.AppSettings["MemcachedServers"].Split(',');
(3)如今咱們先重啓Memcached1(192.168.80.10)的Memcached服務,清空已經緩存的數據內容,確保兩臺服務器如今都沒有數據;而後,從新運行代碼,再次完成代碼測試,測試結果仍是以下圖,說明咱們配置的兩臺Memcached集羣已經配置成功。
(4)在虛擬機中使用telnet查看每臺服務器具體保存了哪一個Key/Value對,這裏因爲test3和test4均被刪除或已失效,因此只需查看前兩個Key/Value對:
①MemcacheServer1(192.168.80.10):保存了第二個Key/Value對,<test2:lousie>
②MemcacheServer2(192.168.80.11):保存了第一個Key/Value對,<test1:edisonchou>
(5)到此,咱們已經完成了一個最小化的memcached集羣讀寫測試Demo。可是,在實際的開發場景中,遠不只僅是存儲一個字符串,更多的是存儲一個自定義的類的實例對象。這就須要使用到序列化,下面咱們來新加一個類MyObject,讓其做爲可序列化的對象來存儲進Memcached中。注意:須要爲該類加上[Serializable]的特性!
[Serializable] public class MyObject { public int ID { get; set; } public string Name { get; set; } }
而後,在主代碼中添加如下幾行代碼,增長對自定義對象的讀寫測試:
// 06.自定義對象存儲 MyObject myObj = new MyObject(); myObj.ID = 12138; myObj.Name = "愛迪生周"; memClient.Set("test5", myObj); MyObject newMyObj = memClient.Get("test5") as MyObject; Console.WriteLine("Hello,My ID is {0} and Name is {1}", newMyObj.ID, newMyObj.Name);
最後,運行代碼,查看結果以下:
(6)怎麼樣,圓滿完成對自定義對象的讀寫操做吧?如今,咱們再看看這個自定義對象是存到了哪臺服務器上:經查詢,test5是存儲到了MemcacheServer2(192.168.80.11)上。
通過了剛剛一系列的實踐操做,咱們在一個最小化的由兩臺Windows Server搭建的Memcached集羣上進行了讀寫操做測試。那麼,咱們不禁得想要去看看到底Memcached是怎樣進行數據訪問的呢?別急,如今咱們就來看看,由實踐到理論,深刻理解一下。
(1)添加新的鍵值對數據
從圖中能夠看出,Memcached雖然稱爲「分佈式」緩存服務器,但服務器端並無「分佈式」功能,而是徹底由客戶端程序庫實現的。服務端之間沒有任何聯繫,數據存取都是經過客戶端的算法實現的。當客戶端要存取數據時,首先會經過算法查找本身維護的服務器哈希列表,找到對應的服務器後,再將數據存往指定服務器。例如:上圖中應用程序要新增一個<'tokyo',data>的鍵值對,它同過set操做提交給Memcached客戶端,客戶端經過必定的哈希算法(好比:通常的求餘函數或者強大的一致性Hash算法)從服務器列表中計算出一個要存儲的服務器地址,最後將該鍵值對存儲到計算出來的服務器裏邊。
(2)獲取已存在的鍵值對數據
上圖中應用程序想要獲取Key爲‘tokyo’(東京這麼熱,還要取它的值是幹神馬呢?)的Value,因而它向Memcached客戶端提交了一個Get請求,Memcached客戶端仍是經過算法從服務器列表查詢哪臺服務器存有Key爲‘tokyo’的Value(即選擇剛剛Set到了哪臺服務器),若是查到,則向查到的服務器請求返回Key爲‘tokyo’的數據。
(3)Memcached分佈式的核心—一致性Hash算法
一致性Hash算法是分佈式緩存的核心理論,我也學習得不深刻,也只是剛剛瞭解了一下,後面我有空深刻學習一下,再單獨寫一篇博文來介紹它,並使用C#來粗略地實現一下這個算法。如今我就簡單地介紹一下,其實這部份內容我以前寫入了個人另外一篇博文《大型網站技術架構讀書筆記之網站的可伸縮架構》中,有興趣的朋友也能夠去看看這篇文章。
首先,簡單的路由算法(經過使用餘數Hash)沒法知足業務發展時服務器擴容的須要:緩存命中率降低。例如:當3臺服務器擴容至4臺時,採用普通的餘數Hash算法會致使大約75%(3/4)被緩存了的數據沒法正確命中,隨着服務器集羣規模的增大,這個比例會線性地上升。那麼,能夠想象,當100臺服務器的集羣中加入一臺服務器,不能命中的機率大概是99%(N/N+1),這個結果顯然是沒法接受的。那麼,可否經過改進路由算法,使得新加入的服務器不影響大部分緩存數據的正確性呢?請看下面的一致性Hash算法。
一致性Hash算法經過一個叫作一致性Hash環的數據結構實現KEY到緩存服務器的Hash映射,以下圖所示:
具體算法過程是:
①先構造一個長度爲0~2^32(2的32次冪)個的整數環(又稱:一致性Hash環),根據節點名稱的Hash值將緩存服務器節點放置在這個Hash環中,如上圖中的node1,node2等;
②根據須要緩存的數據的KEY值計算獲得其Hash值,如上圖中右半部分的「鍵」,計算其Hash值後離node2很近;
③在Hash環上順時針查找距離這個KEY的Hash值最近的緩存服務器節點,完成KEY到服務器的Hash映射查找,如上圖中離右邊這個鍵的Hash值最近的順時針方向的服務器節點是node2,所以這個KEY會到node2中讀取數據;
當緩存服務器集羣須要擴容的時候,只須要將新加入的節點名稱(如node5)的Hash值放入一致性Hash環中,因爲KEY老是順時針查找距離其最近的節點,所以新加入的節點隻影響整個環中的一部分。以下圖中所示,添加node5後,隻影響右邊逆時針方向的三個Key/Value對數據,只佔整個Hash環中的一小部分。
所以,咱們能夠與以前的普通餘數Hash做對比:採用一致性Hash算法時,當3臺服務器擴容到4臺時,能夠繼續命中原有緩存數據的機率爲75%,遠高於普通餘數Hash的25%,並且隨着集羣規模越大,繼續命中原有緩存數據的機率也會隨之增大。當100臺服務器增長1臺時,繼續命中的機率是99%。雖然,仍有小部分數據緩存在服務器中沒法被讀取到,可是這個比例足夠小,經過訪問數據庫也不會對數據庫形成致命的負載壓力。
在本篇我首先花了大力氣來介紹如何使用Memcached客戶端在.Net中進行經常使用的基礎讀寫操做,並經過VMWare Workstation構建了一個由兩臺Windows Server組成的最小化的Memcached服務器集羣。其次,我經過使用C#調用Memcached客戶端,將數據保存到Memcached服務器集羣中,並驗證了是否保存於集羣中。最後,返回到Memcached的數據訪問模型上,從理論到實踐,再從實踐返回到理論,理解Memcached的互不通訊的集羣模式與數據讀寫流程,並簡單瞭解了一下分佈式技術中最核心的算法:一致性Hash算法。
不知不覺都快1:20了,今天就到此停筆關機,洗洗睡了。後面,我會介紹在ASP.NET MVC中應用Memcached來解決登陸狀態的案例,也就是Session會話對象的分佈式存儲。若是你們以爲有用或者有興趣,那就敬請期待了,也請麻煩點個「推薦」,讓我更有動力寫下去,謝謝!
(1)傳智播客馬倫,《Memcached公開課》,http://bbs.itcast.cn/thread-14836-1-1.html
(2)charlee,《Memcached徹底剖析》,http://kb.cnblogs.com/page/42731/
(3)小城歲月,《分佈式緩存Memcached入門》,http://www.cnblogs.com/mecity/archive/2011/06/13/Memcached.html
(4)吸水的技術點點,《分佈式緩存系統Memcached簡介與實踐》,http://www.cnblogs.com/zjneter/archive/2007/07/19/822780.html
(5)源碼工做室,《揭開Socket編程的面紗》,http://goodcandle.cnblogs.com/archive/2005/12/10/294652.aspx
(1)Memcached ClientLib:http://pan.baidu.com/s/1w9Q8I
(2)MemcachedClientDemo:http://pan.baidu.com/s/1hqrDUss