ASP.Net MVC4+Memcached+CodeFirst實現分布式緩存
part 1:給我點時間,允許我感慨一下2016年
正好有時間,總結一下最近使用的一些技術,也算是為2016年畫上一個完美的句號,回顧2016年,感受頗多,感恩那些幫助我的人。展望2017年,我相信一定會遇到一個更好的自己。附上自己喜歡的一張圖片:
好了~~~裝逼結束,下面開始說說如何實現分布式緩存在項目中的應用。
part2:先分析以下需求
軟件架構從單機到分布式遇到的問題(當然這是一個很深的問題,由於能力有限今天就說說如何實現用戶信息的分布式存儲的問題),也就是:走向分布式第一步:多台機器共享用戶登錄狀態,該如何實現?例如:現在有三台機器組成了一個web的應用集群,其中一台機器用戶登錄,然后其他另外兩台機器共享登錄狀態?具體請看下面的圖示:
問題:如果有一個用戶第一次登陸的時候,負載均衡把該用戶分配到IIS1這台服務器上,該用戶的信息就會被保留到IIS1這台服務器上,但是如果該用戶再次訪問其他的web資源的時候,被分配到IIS2上,這是IIS2中,沒有改用戶的信息,會發生什么?該怎么解決?該選用什么介質來保存狀態比較合適?
從圖中可以看出就是保存對應用戶的信息,可能有人會用下面的幾種方法:1)直接保存到進程內session中;2)使用ASP.Net進程外session;3)用數據庫存儲當前登錄狀態;4)微軟的狀態服務器
當然了,使用上面的三種方法不是不可以,但是從網站的整體性能上考慮,確實不太完美,影響系統性能。下面來一一分析這三種方法的缺點:
1)直接保存到進程內session中
缺點:IIS中由於有進程回收機制,系統繁忙的時候session回丟失,IIS重啟也會造成session的丟失,這樣用戶就要重新登錄或重新添加購物車、驗證碼等再放到session中。如果要是把重要敏感的數據放到session中,這是在作死的節奏~~~~
2)使用ASP.Net進程外session 3)用數據庫存儲當前登錄狀態
缺點:這兩種方式效率會比較慢,性能也不是很好,而且無法捕獲session的end事件。
4)微軟的狀態服務器
缺點:性能不好。
下面歡迎Memcached登場!
-
為什么要使用Memcached?
1)高並發訪問數據庫的痛:死鎖
2)磁盤IO之痛
3)讀寫性能完美
4)超簡單的集群搭建Cluster
5)開源
6)性能最佳
7)豐富的成功案例
-
Memcached介紹
Memcached是一個高性能的支持分布式的內存存儲系統。你可以把他看成一個巨大的hash表。形式入:
Key(鍵) Value(值)
唯一鍵值(String) 基本數據(整型, 浮點型,字串,布爾) ,復合類型 (數組, 對象) ,特殊類型(NULL, 不能存放資源), 二進制數據(圖片,視頻,音頻)
注意:Redis在存入對象的時候,不能直接存入,而是要先序列化,然后再存入,使用的時候,再反序列化。
-
Memcached的安裝和配置(在windows安裝)
(1) 下載安裝文件 memcached.exe
(2) 安裝指令cmd>{%mem%}/memcached.exe -d install
(3) 使用cmd>{%mem%}/memcached -d start 【啟動】
如果啟動成功,我們可以使用
cmd>netstat -an
如果發現有一個 11211端口在監聽則說明你的服務OK
補充:
我們也可以把memcached當做一個程序來使用,
cmd>{%mem%}/memcached.exe -p 端口號
(4) 使用telnet工具登錄到Memcached 中進行操作
cmd>telnet 127.0.0.1 11211
注意:Memcached安裝不成功的原因和解決
可能安裝失敗的原因分析
6.1 如果你是用win7,win8系統,他對安全性要求高,因此,需要大家使用管理員的身份來安裝和啟動. 具體是 程序開始===>所有程序==》附件==》cmd(單擊右鍵,選擇以管理員的身份來執行)
6.2 存放memcache.exe 目錄不要有中文或者特殊字符
6.3 安裝成功,但是啟動會報告一個錯誤信息,提示缺少xx.dll ,你可以從別的機器拷貝該dll文件,然后放入到system32下即可,並執行【然后打開“開始-運行-輸入regsvr32 /s MSVCR71.dll”,回車即可解決錯誤提示!】,這是因為有些電腦上裝的操作系統是閹割版的。
6.4 如果上面三個方法都不可以,你可以直接這樣啟動mem
cmd>memcached.exe -p 端口 【這種方式不能關閉窗口】
-
Memcached在Linux下的安裝(推薦安裝方式)
(1)到官網下載安裝包(一定要有版本意識)
http://libevent.org/
http://memcached.org/downloads
不想到官網下載的,我已准備最新版本的安裝包:
libevent-2.1.8-stable.tar.gz 【Linux下處理多並發的核心庫文件】
memcached-1.5.1.tar.gz 【memcached主安裝文件】
(2)開始安裝
這里要注意:一定要先安裝:libevent-2.1.8-stable.tar.gz 文件
解壓:tar -zxvf libevent-2.1.8-stable.tar.gz
cd libevent-2.1.8-stable/ 執行: sudo ./configure --prefix=/usr/lib
執行成功后,再執行:sudo make && sudo make install 編譯和安裝成功之后我們要檢查一下是否安裝成功。
執行:s -l /usr/lib/lib | grep 'libevent' 注意按說應該在/usr/lib目錄下面的,但是目錄路徑是不正確的,這回導致下面的安裝會出現問題,這里先賣下關子,先安裝。
有下面圖片中的文件說明安裝成功了。
接下來我們安裝:memcached-1.5.1.tar,先解壓,如下圖所示:
tar -zxvf memcached-1.5.1.tar.gz
cd memcached-1.5.1
sudo ./configure --with-libevent=/usr/lib/lib 注意這里的路徑要和上面路徑一致。
但是,安裝的過程中出現了錯誤,所以還是老老實實把/usr/lib/lib 該為/usr/lib,但是該目錄下面沒有/usr/lib/lib 下面的文件,不要緊,我們把這些文件cp到/usr/lib目錄下
再執行:sudo ./configure --with-libevent=/usr/lib
sudo make && sudo make install
這樣就安裝成功了,如果還是安裝不成功的,可以留言。看到了就會及時回復大家。
然后,啟動Linux上的Memcached服務:
接着執行下圖中划的指令,注意:第一p是小寫的,第二P是大寫的。
./memcache -d -m 40 -l 127.0.0.1 -u root -p 11210 -P /tmp/memcached.pid
./memcache -d -m 40 -l 0.0.0.0 -u root -p 11210 -P /tmp/memcached.pid
解釋
-d : 表示是一個后台服務程序
-m : 表示memcached占用多少的內存單位是M(如果存儲的數據超過了,則還可以存儲數據,mem會把之前的數據刪掉)
-l : 表示監聽的地址
-p : 監聽的端口
-P : 進程號文件
關閉memcached 服務
killall memcache
kill -9 `cat /tmp/memcached.pid` 注意前后的點不是單引號,而是` 鍵盤最左邊 ESC 下面的。
到此已經安裝成功了。
C#測試:
先開啟服務:
新建一個控制台程序:
Install-Package EnyimMemcached
1 using Enyim.Caching; 2 using Enyim.Caching.Configuration; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace LinuxMemecahed 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 MemcachedClientConfiguration mcConfig = new MemcachedClientConfiguration(); 16 mcConfig.AddServer("192.168.0.109:11211"); 17 using (MemcachedClient client=new MemcachedClient (mcConfig)) 18 { 19 client.Store(Enyim.Caching.Memcached.StoreMode.Set,"name","guozheng"); 20 } 21 22 Console.WriteLine("Ok"); 23 Console.ReadKey(); 24 } 25 } 26 }
使用telnet 鏈接 Memcached :
OK測試成功!!!
-
使用Telnet操作Memcached
首先要登錄到mem上
cmd>telnet 127.0.0.1 11211
(1 ) 添加
add key 0 有效時間 數據大小
舉例
add key1 0 60 5
(2) 查詢
get key
舉例
get key1
(3) 修改
有兩種
set key 0 效時間 數據大小
【說明這時,key如果存在,則是修改,否則就是添加】
舉例
set key1 0 60 5
replace key 0 效時間 數據大小
【說明這時,key如果存在,則是修改,否則就失敗】
(4) 刪除
delete key
舉例
delete key1
還有一種方式:
flush_all
(1) 查看mem的使用狀態
為了大家可以詳細了解,再附上一張圖片:
(2) 其它指令
-
Memcached機制深入了解
1)基於c/s架構,協議簡單:
c/s架構,此時memcached為服務器端,我們可以使用如PHP,c/c++等程序連接memcached服務器。
memcached的服務器客戶端通信並不使用XML等格式,而使用簡單的基於文本行的協議。因此,通過telnet也能在memcached上保存數據、取得數據。
2)內存處理的算法:
本質就是一個大的哈希表。key最大長度是255個字符。
內存模型:Memcache預先將可支配的內存空間進行分區(Slab),每個分區里再分成多個塊(Chunk)最大1MB,但同一個分區里:塊的長度(bytes)是固定的。插入數據時通過一致性哈希算法查找適合自己長度的塊,然后插入,會有內存浪費。
為了提高性能,memcached中保存的數據都存儲在memcached內置的內存存儲空間中。由於數據僅存在於內存中,因此重啟memcached、重啟操作系統會導致全部數據消失。另外,內容容量達到指定值之后,就基於LRU(Least Recently Used[最近最少使用算法])算法自動刪除不使用的緩存。memcached本身是為緩存而設計的服務器,因此並沒有過多考慮數據的永久性問題。
3)惰性刪除:
它並沒有提供監控數據過期的機制,而是惰性的,當查詢到某個key數據時,如果過期那么直接拋棄。
4)集群搭建原理:
Memcache服務器端並沒有提供集群功能,但是通過客戶端的驅動程序實現了集群配置。
客戶端實現集群的原理:首先客戶端配置多台集群機器的ip和端口的列表。然后客戶端驅動程序在寫入之前,首先對key做哈希處理得到哈希值后對總的機器的個數進行取余然后就選擇余數對應的機器。
5)基於客戶端的分布式
6) 基於libevent的事件處理(這就是Memcached為什么這么吊的原因)
libevent是一套跨平台的事件處理接口的封裝,能夠兼容包括這些操作系統:Windows/Linux/BSD/Solaris 等操作系統的的事件處理。Memcached 使用libevent來進行網絡並發連接的處理,能夠保持在很大並發情況下,仍舊能夠保持快速的響應能力。
part3:項目實戰(demo版)
還記得上面提到的問題嗎?如何把用戶的狀態信息保存起來,共享給這三台服務器?下面通過代碼,給大家介紹ASP.Net MVC 4中如何使用Memcached,開始吧!
項目結構:
項目中需要引用Memcached的dll,如下:
1、首先准備好工具類:
MemcacheHelper:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using Memcached.ClientLibrary; 6 7 namespace WebDemo.Models 8 { 9 public static class MemcacheHelper 10 { 11 private static MemcachedClient mc; 12 13 static MemcacheHelper() 14 { 15 //通過客戶端來進行memcached的集群配置,在插入數據的時候,使用一致性哈希算法,將對應的value值存入Memcached 16 String[] serverlist = { "127.0.0.1:11211" }; 17 18 // 初始化Memcached的服務池 19 SockIOPool pool = SockIOPool.GetInstance("test"); 20 //設置服務器列表 21 pool.SetServers(serverlist); 22 //各服務器之間負載均衡的設置比例 23 pool.SetWeights(new int[] { 1 }); 24 pool.Initialize(); 25 //創建一個Memcached的客戶端對象 26 mc = new MemcachedClient(); 27 mc.PoolName = "test"; 28 //是否啟用壓縮數據:如果啟用了壓縮,數據壓縮長於門檻的數據將被儲存在壓縮的形式 29 mc.EnableCompression = false; 30 31 } 32 /// <summary> 33 /// 插入值 34 /// </summary> 35 /// <param name="key">建</param> 36 /// <param name="value">值</param> 37 /// <param name="expiry">過期時間</param> 38 /// <returns></returns> 39 public static bool Set(string key, object value,DateTime expiry){ 40 return mc.Set(key, value, expiry); 41 } 42 /// <summary> 43 /// 獲取值 44 /// </summary> 45 /// <param name="key"></param> 46 /// <returns></returns> 47 public static object Get(string key) 48 { 49 return mc.Get(key); 50 } 51 } 52 }
BaseController:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using WebDemo.Models; namespace WebDemo.Controllers { public class BaseController : Controller { //用來保存當前的用戶信息 public UserInfo LoginUser { get; set; } //通過過濾器來實現每個頁面的檢查 protected override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); //從cookie中獲取咱們的 登錄的sessionId string sessionId = Request["sessionId"]; //如果sessionid為空值,則跳轉到登錄頁面 if (string.IsNullOrEmpty(sessionId)) { //return RedirectToAction("Login", "Logon"); Response.Redirect("/Logon/Index"); } object obj = MemcacheHelper.Get(sessionId); UserInfo user = obj as UserInfo; if (user == null) { Response.Redirect("/Logon/Index"); } LoginUser = user; //實現session的滑動機制 MemcacheHelper.Set(sessionId, user, DateTime.Now.AddMinutes(20)); } } }

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Memcached.ClientLibrary; namespace WebDemo.Controllers { public class MemcachedController : BaseController { // // GET: /Memcached/ public ActionResult Index() { //初始化memcached 服務器端集群列表。 String[] serverlist = { "127.0.0.1:11211"}; // initialize the pool for memcache servers SockIOPool pool = SockIOPool.GetInstance("test"); //設置怎么mem池連接點服務器端。 pool.SetServers(serverlist); pool.Initialize(); //創建了一個mem客戶端的代理類。 var mc = new MemcachedClient(); mc.PoolName = "test"; mc.EnableCompression = false; //mc.Add("gz1", "我的女神宋智孝"); mc.Set("gz2", "hahaha", DateTime.Now.AddSeconds(15)); pool.Shutdown();//關閉連接池 return Content("ok"); } } }
2、models:

using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; namespace WebDemo.Models { public class SchoolDbContext :DbContext { //使用EF的code-first,如果數據庫中沒有數據名字為MySqlDemo,則調用CreateIfNotExists方法會創建數據庫 public SchoolDbContext() : base("name=MySqlDemo") { this.Database.CreateIfNotExists(); } public virtual DbSet<Student> Student { get; set; } public virtual DbSet<UserInfo> UserInfo { get; set; } } }

using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; namespace WebDemo.Models { [Serializable] public class Student { [StringLength(32)] public virtual string SName { get; set; } [StringLength(32)] public virtual string Address { get; set; } [Key] public virtual int Id { get; set; } } }

using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Web; namespace WebDemo.Models { [Serializable] public class UserInfo { public string UName { get; set; } [Required] [MaxLength(32)] public string UPwd { get; set; } [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int UserId { get; set; } } }
3、接下來的代碼是使用分布式緩存中最關鍵的一點:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using WebDemo.Models; namespace WebDemo.Controllers { public class LogonController : Controller { // // GET: /Logon/ public ActionResult Index() { return View(); } public ActionResult Login(UserInfo user) { //創建一個DbContext對象,這樣寫不是很合理,先留個問題。(使用EF的code-first時需要注意的點) SchoolDbContext dbContext =new SchoolDbContext(); var loginUser = dbContext.UserInfo.Where(u => u.UName.Equals(user.UName) && u.UPwd.Equals(user.UPwd)).FirstOrDefault(); if (loginUser == null) { return Content("用戶名密碼錯誤!"); } else { Guid sessionId = Guid.NewGuid();//申請了一個模擬的GUID:SessionId //把sessionid寫到客戶端瀏覽器里面去了(一定要把sessionid寫到客戶端,這樣用戶在訪問其他web資源的時候,就會把cookie中的信息傳給服務器,然后通過sessionid的key到Memcached中去取對應的值) Response.Cookies["sessionId"].Value = sessionId.ToString(); //再把用戶的信息插入到Memcached中 MemcacheHelper.Set(sessionId.ToString(), loginUser, DateTime.Now.AddMinutes(20)); return Content("ok"); } } public ActionResult ValidateCode() { ValidateCodeHelper helper =new ValidateCodeHelper(); string strCode = helper.CreateValidateCode(4); Session["validateCode"] = strCode; var byteData = helper.CreateValidateGraphic(strCode); return File(byteData, "image/jpeg"); } } }
到這里利用ASP.Net MVC4、EF(code-first)、Memcached實現分布式緩存的功能基本完成了,功能很簡單,但是很值得大家體會這里面的思想。最后,為了大家好理解如何實現分布式緩存,我把demo版本的流程再給大家梳理一遍,希望對你有用。
圖一:客戶端是如何把數據插入到服務器端的:
圖二:demo版流程:
最后再補充一點:
1.1 memcached的數據生命周期
當一個鍵值對存放到mem中,在以下情況將會被銷毀
(1) 時間到(生命周期從存放時就開始計算)
(2) 你使用delete函數,刪除 flush_all
(3) 重啟mem服務
(4) 重啟系統
1.2 memcached插入數據的原則(說的不全,希望大家多多包涵)
(1) 變化頻繁,具有不穩定性的數據,不需要實時入庫。(比如在線人數,在線狀態,用戶評分)
(2) 門戶網站的新聞,覺得頁面靜態化不能滿足需求,可以放入到mem中。(配合JQuery的AJAX請求)
1.3 什么樣的數據不適合放入memcached中
(1)過大的數據、特別重要的數據
代碼:鏈接:http://pan.baidu.com/s/1jI3BUPw 密碼:djs5
如果覺得好的話,希望大家推薦一下哈~~~,后期還有更新。希望大家可以交流交流,這樣都有進步,不要再留郵箱哦
注:轉載請注明作者,謝謝。