分布式緩存學習總結
來自: http://www.cnblogs.com/joeymary/p/5180517.html
一、分布式緩存簡圖
二、為什么使用 Memcached 分布式緩存呢?
三、Memcached 基礎原理
四、Memcache 下載與安裝
五、MencacheHelper.cs 示例使用 結合 Session 與項目配置緩存
六、Redis 和 Memcache 的區別總結
一、分布式緩存簡圖
二、為什么使用 Memcached 分布式緩存呢?
首先先講講為何要緩存,在數據驅動的 web 開發中,經常要重復從數據庫中取出相同的數據,這種重復極大的增加了數據庫負載。緩存是解決這個問題的好辦法。但是 ASP.NET 中的雖然已經可以實現對頁面局部進行緩存,但還是不夠靈活。 Memcached 應運而生。
1 、高并發訪問數據庫的痛楚:死鎖!
2 、磁盤 IO 之痛,數據庫讀寫說白了就是跟磁盤打交道,磁盤讀取速度是有限制的,一般高點也就 7200 轉
3 、多客戶端共享緩存
4 、 Net+Memory >> IO
5 、讀寫性能完美 1s 讀取可以達到 1w 次 寫: 10w 次
6 、超簡單集群搭建 Clister
7 、開源 Open Source
8 、沒有提供主從賦值功能,也沒提供容災等功能(容災:即數據備份能使意外發生后恢復數據, Memcached 不會進行備份,由于是緩存在內存中的,一斷電就會失去數據
),所以所有的代碼基本都只考慮性能最佳。如要考慮容災,則可使用 Redis 分布式緩存
9 、學習成本非常低,入門非常容易
10 、豐富的成功的案例。很多大型公司都是用這個來做分布式緩存
注: Memcached 在企業中一般都是在 linux 下跑,才能達到性能最佳。
三、Memcached 基礎原理
底層通信是使用 Socket
可以將緩存服務器理解為 Socket 服務端,將 WEB 服務器理解為客戶端。
四、Memcache 下載與安裝
下載,百度一下或者直接在 csdn 上搜一下 windows Memcache 穩定版就行, 0 積分。
1 、下載完后,就這么個 exe
2 、安裝,敲幾行 cmd 命令就行,截圖如下,左邊是我們計算機服務列表,可以看到,已經啟動了我們的 Memcached ( Memcache 是這個開源項目的名稱,加個 d , Memcached 是具體這個應用程序,也就是這個 exe 的名稱)
3、現在已經啟動服務了,且將其安裝到 windows 服務上了,這樣一來,就不用每次手動去啟動了,隨電腦而啟動。
現在來測試下,隨便新建個控制臺應用程序
1 using Memcached.ClientLibrary; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace 測試 9 { 10 public class MemcacheTset 11 { 12 public static void Test() 13 { 14 string[] serverlist = { "127.0.0.1:11211" }; //服務器列表,可多個 用逗號隔開
15
16 //初始化池 17 SockIOPool pool = SockIOPool.GetInstance(); 18 19 //根據實際情況修改下面參數 20 pool.SetServers(serverlist); 21 pool.InitConnections = 3; 22 pool.MinConnections = 3; 23 pool.MaxConnections = 5; 24 pool.SocketConnectTimeout = 1000; 25 pool.SocketTimeout = 3000; 26 pool.MaintenanceSleep = 30; 27 pool.Failover = true; 28 pool.Nagle = false; 29 pool.Initialize(); // initialize the pool for memcache servers
30
31 //獲得客戶端實例 32 MemcachedClient mc = new MemcachedClient();//初始化一個客戶端 33 mc.EnableCompression = false; 34 35 Console.WriteLine("---------測試----------"); 36 mc.Set("test","my value");//存儲數據到緩存服務器,這里將字符串"my value"緩存,key 37 38 if (mc.KeyExists("test"))//測試緩存存在key為test的項目 39 { 40 Console.WriteLine("test is Exists"); 41 Console.WriteLine(mc.Get("test").ToString());//在緩存中獲取key為test的項目 42 } 43 else 44 { 45 Console.WriteLine("test not Exists"); 46 } 47 48 Console.ReadLine(); 49 50 mc.Delete("test");//移除緩存中key為test的項目 51 52 if (mc.KeyExists("test")) 53 { 54 Console.WriteLine("test is Exists"); 55 Console.WriteLine(mc.Get("test").ToString()); 56 } 57 else 58 { 59 Console.WriteLine("test not Exists"); 60 } 61 62 Console.ReadLine(); 63 64 SockIOPool.GetInstance().Shutdown();//關閉池,關閉sockets 65 } 66 } 67 }View Code</pre>
如果程序運行正常,說明我們的Memcache服務已啟動且運行正常。
有一點,別忘咯,就是我們Memcache的驅動,同樣,百度C# Memcache安裝就OK。
好了,現在我們就實際來應用下吧。
![]()
五、MencacheHelper.cs 示例使用 結合 項目配置緩存
對于什么是項目配置,相信大家肯定都熟悉的,就是將業務中不經常更改的數據如系統郵件地址,將其存在數據庫中,方便更改。
然后,回頭看這幾個字,“不經常更改”,我們就得注意了,這樣的數據,我們就得想到緩存了,緩存就是用來管理這樣的數據。然后呢,我們就可以使用我們的Memcache來管理它了。
項目中,我們可以新建個MemcacheHelper類來封裝下代碼,我寫了個最簡單的存取刪除。
1 using Memcached.ClientLibrary; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace JOEY.BookShop.Common 9 { 10 public class MemcacheHelper 11 { 12 private static readonly MemcachedClient mc; 13 static MemcacheHelper() 14 { 15 string[] serverlist = { "127.0.0.1:11211" }; //服務器列表,可多個 用逗號隔開
16 17 //初始化池 18 SockIOPool pool = SockIOPool.GetInstance(); 19 20 //根據實際情況修改下面參數 21 pool.SetServers(serverlist); 22 pool.InitConnections = 3; 23 pool.MinConnections = 3; 24 pool.MaxConnections = 5; 25 pool.SocketConnectTimeout = 1000; 26 pool.SocketTimeout = 3000; 27 pool.MaintenanceSleep = 30; 28 pool.Failover = true; 29 pool.Nagle = false; 30 pool.Initialize(); // initialize the pool for memcache servers
31 32 //獲得客戶端實例 33 mc = new MemcachedClient();//初始化一個客戶端 34 mc.EnableCompression = false; 35 } 36 37 /// <summary> 38 /// 存 39 /// </summary> 40 /// <param name="key"></param> 41 /// <param name="value"></param> 42 public static void Set(string key, object value) 43 { 44 mc.Set(key, value); 45 } 46 47 public static void Set(string key, object value, DateTime time) 48 { 49 mc.Set(key, value, time); 50 } 51 52 /// <summary> 53 /// 取 54 /// </summary> 55 /// <param name="key"></param> 56 /// <returns></returns> 57 public static object Get(string key) 58 { 59 if (mc.KeyExists(key)) 60 { 61 return mc.Get(key); 62 } 63 else 64 { 65 return null; 66 } 67
68 } 69 70 /// <summary> 71 /// 刪除 72 /// </summary> 73 /// <param name="key"></param> 74 /// <returns></returns> 75 public static bool Delete(string key) 76 { 77 if (mc.KeyExists(key)) 78 { 79 mc.Delete(key); 80 return true; 81 } 82 return false; 83 } 84 } 85 }View Code</pre>
1 var setting = this.DbSession.SettingsDal.LoadEntities(c => c.Name == "系統郵件地址").FirstOrDefault(); 2 string value = setting.Value.Trim(); 3 MemcacheHelper.Set("setting_" + key, value);這樣就將我們的從數據庫中取出的系統郵件地址存儲到了Memcache中,很方便吧。取的話就是:
object obj = MemcacheHelper.Get("setting_" + "系統郵件地址");這里由于存取的數據均為字符串,不存在序列化的問題,如果存取的對象類型不是字符串,如某個表Model,那么就得通過序列化來進行操作,對于序列化,本人是使用Json.Net來操作。
再來個輔助類吧。
1 using Newtonsoft.Json; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace JOEY.BookShop.Common 9 { 10 /// <summary> 11 /// Json.Net 序列化,對于由于相互引用類型導致的序列化死循環,可在該對象上加個特性標簽[JsonIgnore] 如在Model中有外鍵,兩個模型間相互引用即造成該問題 12 /// </summary> 13 public class SerializeHelper 14 { 15 /// <summary> 16 /// 傳入對象,序列化成字符串返回 17 /// </summary> 18 /// <param name="obj"></param> 19 /// <returns></returns> 20 public static string SerializeToString(object obj) 21 { 22 return JsonConvert.SerializeObject(obj); 23 } 24 /// <summary> 25 /// 傳入序列化字符串,反序列化成對應對象返回 26 /// </summary> 27 /// <typeparam name="T">泛型,對應對象類型</typeparam> 28 /// <param name="serializeStr">序列化后的字符串</param> 29 /// <returns></returns> 30 public static T DeserializeToObject<T>(string serializeStr) 31 { 32 return JsonConvert.DeserializeObject<T>(serializeStr); 33 } 34 } 35 }View Code</pre>
很容易就能看懂,對吧。當然驅動也是需要的。同樣百度哦。
這里有個小問題,這個程序集,Newtonsoft.Json在我的MVC項目中本身就存在,而我在其他項目(即項目Common)中用的時候用的網上下的,選的版本是4.5,由于MVC項目引用了Common,這樣貌似就出現了版本不一致的情況,貌似是這樣,會提示錯誤。于是我把MVC中的dll給刪除了,重新加載Common下的dll(現在想想我為什么不把Common下的刪了,去引用MVC下這個呢- -),這樣一來,又出現一個問題,未能加載文件或程序集“Newtonsoft.Json,Version=4.5.0.0。估計是配置項的原因,于是,百度了下,在web.config runtime節點下添加了這么幾行,修改后為:
<dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" /> </dependentAssembly>8.0.0.0是當前這個dll的版本,估計是版本更新通知吧。具體為何這樣做是有點迷糊的。誰能指點下呢,感激~~~
六、 Redis 和 Memcache 的區別總結(摘自百度知道)
1. Redis 是什么
這個問題的結果影響了我們怎么用 Redis 。如果你認為 Redis 是一個 key value store, 那可能會用它來代替 MySQL ;如果認為它是一個可以持久化的 cache, 可能只是它保存一些頻繁訪問的臨時數據。 Redis 是 REmote DIctionary Server 的縮寫,在 Redis 在官方網站的的副標題是 A persistent key-value database with built-in net interface written in ANSI-C for Posix systems ,這個定義偏向 key value store 。還有一些看法則認為 Redis 是一個 memory database ,因為它的高性能都是基于內存操作的基礎。另外一些人則認為 Redis 是一個 data structure server ,因為 Redis 支持復雜的數據特性,比如 List, Set 等。對 Redis 的作用的不同解讀決定了你對 Redis 的使用方式。
互聯網數據目前基本使用兩種方式來存儲,關系數據庫或者 key value 。但是這些互聯網業務本身并不屬于這兩種數據類型,比如用戶在社會化平臺中的關系,它是一個 list ,如果要用關系數據庫存儲就需要轉換成一種多行記錄的形式,這種形式存在很多冗余數據,每一行需要存儲一些重復信息。如果用 key value 存儲則修改和刪除比較麻煩,需要將全部數據讀出再寫入。 Redis 在內存中設計了各種數據類型,讓業務能夠高速原子的訪問這些數據結構,并且不需要關心持久存儲的問題,從架構上解決了前面兩種存儲需要走一些彎路的問題。
2. Redis 不可能比 Memcache 快
很多開發者都認為 Redis 不可能比 Memcached 快, Memcached 完全基于內存,而 Redis 具有持久化保存特性,即使是異步的, Redis 也不可能比 Memcached 快。但是測試結果基本是 Redis 占絕對優勢。一直在思考這個原因,目前想到的原因有這幾方面。
Libevent 。和 Memcached 不同, Redis 并沒有選擇 libevent 。 Libevent 為了迎合通用性造成代碼龐大 ( 目前 Redis 代碼還不到 libevent 的 1/3) 及犧牲了在特定平臺的不少性能。 Redis 用 libevent 中兩個文件修改實現了自己的 epoll event loop(4) 。業界不少開發者也建議 Redis 使用另外一個 libevent 高性能替代 libev ,但是作者還是堅持 Redis 應該小巧并去依賴的思路。一個印象深刻的細節是編譯 Redis 之前并不需要執行 ./configure 。
CAS 問題。 CAS 是 Memcached 中比較方便的一種防止競爭修改資源的方法。 CAS 實現需要為每個 cache key 設置一個隱藏的 cas token , cas 相當 value 版本號,每次 set 會 token 需要遞增,因此帶來 CPU 和內存的雙重開銷,雖然這些開銷很小,但是到單機 10G+ cache 以及 QPS 上萬之后這些開銷就會給雙方相對帶來一些細微性能差別 (5) 。
3. 單臺 Redis 的存放數據必須比物理內存小
Redis 的數據全部放在內存帶來了高速的性能,但是也帶來一些不合理之處。比如一個中型網站有 100 萬注冊用戶,如果這些資料要用 Redis 來存儲,內存的容量必須能夠容納這 100 萬用戶。但是業務實際情況是 100 萬用戶只有 5 萬活躍用戶, 1 周來訪問過 1 次的也只有 15 萬用戶,因此全部 100 萬用戶的數據都放在內存有不合理之處, RAM 需要為冷數據買單。
這跟操作系統非常相似,操作系統所有應用訪問的數據都在內存,但是如果物理內存容納不下新的數據,操作系統會智能將部分長期沒有訪問的數據交換到磁盤,為新的應用留出空間。現代操作系統給應用提供的并不是物理內存,而是虛擬內存 (Virtual Memory) 的概念。
基于相同的考慮, Redis 2.0 也增加了 VM 特性。讓 Redis 數據容量突破了物理內存的限制。并實現了數據冷熱分離。
4. Redis 的 VM 實現是重復造輪子
Redis 的 VM 依照之前的 epoll 實現思路依舊是自己實現。但是在前面操作系統的介紹提到 OS 也可以自動幫程序實現冷熱數據分離, Redis 只需要 OS 申請一塊大內存, OS 會自動將熱數據放入物理內存,冷數據交換到硬盤,另外一個知名的“理解了現代操作系統 (3) ”的 Varnish 就是這樣實現,也取得了非常成功的效果。
作者 antirez 在解釋為什么要自己實現 VM 中提到幾個原因 (6) 。主要 OS 的 VM 換入換出是基于 Page 概念,比如 OS VM1 個 Page 是 4K, 4K 中只要還有一個元素即使只有 1 個字節被訪問,這個頁也不會被 SWAP, 換入也同樣道理,讀到一個字節可能會換入 4K 無用的內存。而 Redis 自己實現則可以達到控制換入的粒度。另外訪問操作系統 SWAP 內存區域時 block 進程,也是導致 Redis 要自己實現 VM 原因之一。
5. 用 get/set 方式使用 Redis
作為一個 key value 存在,很多開發者自然的使用 set/get 方式來使用 Redis ,實際上這并不是最優化的使用方法。尤其在未啟用 VM 情況下, Redis 全部數據需要放入內存,節約內存尤其重要。
假如一個 key-value 單元需要最小占用 512 字節,即使只存一個字節也占了 512 字節。這時候就有一個設計模式,可以把 key 復用,幾個 key-value 放入一個 key 中, value 再作為一個 set 存入,這樣同樣 512 字節就會存放 10-100 倍的容量。
這就是為了節約內存,建議使用 hashset 而不是 set/get 的方式來使用 Redis ,詳細方法見參考文獻 (7) 。
6. 使用 aof 代替 snapshot
Redis 有兩種存儲方式,默認是 snapshot 方式,實現方法是定時將內存的快照 (snapshot) 持久化到硬盤,這種方法缺點是持久化之后如果出現 crash 則會丟失一段數據。因此在完美主義者的推動下作者增加了 aof 方式。 aof 即 append only mode ,在寫入內存數據的同時將操作命令保存到日志文件,在一個并發更改上萬的系統中,命令日志是一個非常龐大的數據,管理維護成本非常高,恢復重建時間會非常長,這樣導致失去 aof 高可用性本意。另外更重要的是 Redis 是一個內存數據結構模型,所有的優勢都是建立在對內存復雜數據結構高效的原子操作上,這樣就看出 aof 是一個非常不協調的部分。
其實 aof 目的主要是數據可靠性及高可用性,在 Redis 中有另外一種方法來達到目的: Replication 。由于 Redis 的高性能,復制基本沒有延遲。這樣達到了防止單點故障及實現了高可用。
小結
要想成功使用一種產品,我們需要深入了解它的特性。 Redis 性能突出,如果能夠熟練的駕馭,對國內很多大型應用具有很大幫助。
</div>