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

若是以为好的话,但愿你们推荐一下哈~~~,后期还有更新。但愿你们能够交流交流,这样都有进步,不要再留邮箱哦

注:转载请注明做者,谢谢。

相关文章
相关标签/搜索