ASP.Net MVC4+Memcached+CodeFirst實現分佈式緩存

       ASP.Net MVC4+Memcached+CodeFirst實現分佈式緩存

part 1:給我點時間,容許我感慨一下2016年

  正好有時間,總結一下最近使用的一些技術,也算是爲2016年畫上一個完美的句號,回顧2016年,感覺頗多,感恩那些幫助個人人。展望2017年,我相信必定會遇到一個更好的本身。附上本身喜歡的一張圖片:c++

  

好了~~~裝逼結束,下面開始說說如何實現分佈式緩存在項目中的應用。web

part2:先分析如下需求

  軟件架構從單機到分佈式遇到的問題(固然這是一個很深的問題,因爲能力有限今天就說說如何實現用戶信息的分佈式存儲的問題),也就是:走向分佈式第一步:多臺機器共享用戶登陸狀態,該如何實現?例如:如今有三臺機器組成了一個web的應用集羣,其中一臺機器用戶登陸,而後其餘另外兩臺機器共享登陸狀態?具體請看下面的圖示:算法

問題:若是有一個用戶第一次登錄的時候,負載均衡把該用戶分配到IIS1這臺服務器上,該用戶的信息就會被保留到IIS1這臺服務器上,可是若是該用戶再次訪問其餘的web資源的時候,被分配到IIS2上,這是IIS2中,沒有改用戶的信息,會發生什麼?該怎麼解決?該選用什麼介質來保存狀態比較合適?數據庫

 從圖中能夠看出就是保存對應用戶的信息,可能有人會用下面的幾種方法:1)直接保存到進程內session中;2)使用ASP.Net進程外session;3)用數據庫存儲當前登陸狀態;4)微軟的狀態服務器windows

固然了,使用上面的三種方法不是不能夠,可是從網站的總體性能上考慮,確實不太完美,影響系統性能。下面來一一分析這三種方法的缺點:數組

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,以下:

一、首先準備好工具類:

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 }
View Code

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));
        }
       
    }
}
View Code

 

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");
        }

    }
}
View Code

 

二、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; }
    }
}
View Code
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; }
    }
}
View Code
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; }
    }
}
View Code

三、接下來的代碼是使用分佈式緩存中最關鍵的一點:

 

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");
        }
    }
}
View Code

到這裏利用ASP.Net MVC四、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

若是以爲好的話,但願你們推薦一下哈~~~,後期還有更新。但願你們能夠交流交流,這樣都有進步,不要再留郵箱哦

注:轉載請註明做者,謝謝。

相關文章
相關標籤/搜索