Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作


一、Memcached ClientLib For .Net

  首先,不得不說,許多語言都實現了連接Memcached的客戶端,其中以Perl、PHP為主。 僅僅memcached網站上列出的語言就有:Perl、PHP、Python、Ruby、C#、C/C++以及Lua等。

  那么,我們作為.Net碼農,自然是使用C#。既然Memcached客戶端有.Net版,那我們就去下載一個來試試。

  下載文件: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服務器進行通信了,爽歪歪啊。

二、在.Net中進行Memcached基本操作

2.1 基本的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)現在我們通過調試,查看這段代碼的結果:

2.2 進階的Memcached客戶端操作

  (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集群已經配置成功。

result

  (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)上。

三、回頭再看Memcached數據訪問模型

  經過了剛剛一系列的實踐操作,我們在一個最小化的由兩台Windows Server搭建的Memcached集群上進行了讀寫操作測試。那么,我們不由得想要去看看到底Memcached是怎樣進行數據訪問的呢?別急,現在我們就來看看,由實踐到理論,深入理解一下。

  (1)添加新的鍵值對數據

基於客戶端的分布式

  從圖中可以看出,Memcached雖然稱為“分布式”緩存服務器,但服務器端並沒有“分布式”功能,而是完全由客戶端程序庫實現的。服務端之間沒有任何聯系,數據存取都是通過客戶端的算法實現的。當客戶端要存取數據時,首先會通過算法查找自己維護的服務器哈希列表,找到對應的服務器后,再將數據存往指定服務器。例如:上圖中應用程序要新增一個<'tokyo',data>的鍵值對,它同過set操作提交給Memcached客戶端,客戶端通過一定的哈希算法(比如:一般的求余函數或者強大的一致性Hash算法)從服務器列表中計算出一個要存儲的服務器地址,最后將該鍵值對存儲到計算出來的服務器里邊。

  (2)獲取已存在的鍵值對數據

Get

  上圖中應用程序想要獲取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映射,如下圖所示:

一致性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環中的一小部分。

node5

  因此,我們可以與之前的普通余數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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM