分布式缓存--MVC+EF+Memcache

来源:互联网 发布:印刷自助报价系统源码 编辑:程序博客网 时间:2024/06/05 04:11

一、从单机到分布式


现在三台机器组成一个Web的应用集群,其中一台机器用户登录,然后其他另外两台机器如何共享登录状态?


解决方案:


1、AspNet进程外的Session 。

2、用数据库存数等钱登录状态。

3、Memcache。


二、为什么用Memcache?


1、解决高并发访问数据库带来的死锁

2、多用户端共享缓存


三、Memcache原理



其实memcache是一种windows服务,客户端发来的请求,都会被Socket服务器端接受到。存数使用键值对存储的。客户端进行存储的时候,就是找最接近value的大小的存储空间来存储,当然就造成了内存浪费,不过利大于弊。


四、安装和配置Memcache


首先我们看没有安装Memcache之前的服务:



可以看到M开头的没有Memcache.exe




我们把下下来的Memcache放在一个目录下,然后在命令窗口下运行这个目录下的Memcache.exe。嫌麻烦的话,可以直接运行。



安装命令:将Memcache.exe安装为Windows服务:Memcache.exe-d install。


启动命令:启动Memcache服务:Memcache.exe-d start。




这样我们就可以在服务器端看到了。



启动Memcache服务(windows命令):netstart "Memcache Server"


停止Memcache服务(windows命令):netstop "Memcache Server"



测试memcache是否连接成功:


启动命令窗口输入:telnet 127.0.0.1 11211

[提示错误:'telnet'不是内部或外部命令,也不是可运行的程序或批处理文件。]

注:windows7带有telnet,只是默认没有安装而已。


解决方法:

 依次打开“开始”→“控制面板”→“打开或关闭Windows功能”,在打开的窗口处,寻找并勾选“Telnet客户端”,然后点击“确定”。顺利安装后,再在运行下输入此命令就OK了。





输入:stats,出现Memcache当前的状态。



添加新记录:addKeyName 0 0 ValueByteLength [回车] ValueContent。


删除记录: delete KeyName。


添加或更新记录: set KeyName 0 0 ValueByteLength [回车] ValueContent。


更新记录: replace KeyName 0 0 ValueByteLength [回车] ValueContent。





OK了。


五、MVC+EF(CodeFirst)+Memcache代码:


EF有三种生成数据库的模式:ModelFirst、EntityFirst、CodeFirst。这里我们用的是CodeFirst,连接Sqlserver的数据库。



我们先看Model中的数据库上下文SchoolDbContext:


<span style="font-size:18px;">using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Web;namespace WebDemo.Models{    public class SchoolDbContext :DbContext    {        public SchoolDbContext()            : base("name=MySqlDemo")        {            this.Database.CreateIfNotExists();    //这里是数据库是否存在,如果不存在,就会新建一个数据库        }        public virtual DbSet<Student> Student { get; set; }    //数据库中的实体        public virtual DbSet<UserInfo> UserInfo { get; set; }    }}</span>

Model中的UserInfo实体:


<span style="font-size:18px;">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; }    }}</span>

Student表就不写了,一样的。我们就只用UserInfo表来测试。


Model中的MemcacheHelper:


<span style="font-size:18px;">using System;using System.Collections.Generic;using System.Linq;using System.Web;using Memcached.ClientLibrary;namespace WebDemo.Models{    public static class MemcacheHelper    {        private static MemcachedClient mc;        static MemcacheHelper()        {             String[] serverlist = { "127.0.0.1:11211" };       //连接的主机和端口号            // initialize the pool for memcache servers            SockIOPool pool = SockIOPool.GetInstance("test");            pool.SetServers(serverlist);            pool.Initialize();            mc = new MemcachedClient();            mc.PoolName = "test";            mc.EnableCompression = false;                    }        /// <summary>        /// 这个方法就是用来传入数据的        /// </summary>        /// <param name="key"></param>        /// <param name="value"></param>        /// <param name="expiry">失效日期</param>        /// <returns></returns>        public static bool Set(string key, object value,DateTime expiry)                {               return mc.Set(key, value, expiry);        }        /// <summary>        /// 这个方法就是用来得到数据的        /// </summary>        /// <param name="key">键值对中的key值</param>        /// <returns></returns>        public static object Get(string key)        {            return mc.Get(key);        }    }}</span>

CodeFirst最主要的就是配置文件了,用asp.net与sqlserver的配置如下:


<span style="font-size:18px;">  <connectionStrings>  <add name="MySqlDemo" connectionString="Data Source=.;user id=sa;password=123456;persist security info=True;database=MySqlDemo" providerName="System.Data.SqlClient" />  </connectionStrings></configuration></span>


这里一定要写对,如果出现这样”Data Source“或”Server“无法识别,就代表这个配置文件是错误的。一定有地方写错了。


Controller中的HomeController:


<span style="font-size:18px;">using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using WebDemo.Models;namespace WebDemo.Controllers{    public class HomeController : BaseController   //进入任何非登录页面,都要集成BaseController这个Controller,继承方法</span><pre name="code" class="csharp"><pre name="code" class="csharp"><span style="font-size:18px;">OnActionExecuting。</span>

{ // // GET: /Home/ /// <summary> /// 返回home/index /// </summary> /// <returns></returns> public ActionResult Index() { return Content("home index"); } }}


对应的View中的Index:


<span style="font-size:18px;">@{    Layout = null;}<!DOCTYPE html><html><head>    <title>Index</title></head><body>    <div>        欢迎登陆!!!!    </div></body></html></span>


Controller中的LogonController跟这个是一样的,就不写了。


<span style="font-size:18px;">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();        }        /// <summary>        /// 登陆信息        /// </summary>        /// <param name="user">User实体</param>        /// <returns></returns>        public ActionResult Login(UserInfo user)        {            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写到客户端浏览器里面去累                Response.Cookies["sessionId"].Value = sessionId.ToString();                                //写入缓存                MemcacheHelper.Set(sessionId.ToString(), loginUser, DateTime.Now.AddMinutes(20));                //用户登录成功之后要保存用户的登录的数据:                //Session["loginUser"] = loginUser;                return Content("ok");            }        }    }}</span>


大家可以看到上面的Response.Cookies["sessionId"]是将sessionid写到客户端浏览器中去的。




看这张图,大家就很明白了吧,当浏览器客户端登陆请求发到服务器端的时候,就会查询是否存在MM分布缓存,如果有就会调用BaseController中的OnActionExecuting方法(这个方法其实就是要所有的Controller执行的时候,都要执行的方法)。其实就是上面图中的后续请求。


Controller中的BaseController(这个类是抽象出来的):


<span style="font-size:18px;">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"];            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;            MemcacheHelper.Set(sessionId, user, DateTime.Now.AddMinutes(20));        }           }}</span>



视图:Logon/Index:


<span style="font-size:18px;">@{    Layout = null;}<!DOCTYPE html><html><head>       <title>后台管理系统登录</title>        <script src="../../Scripts/jquery-1.7.1.js"></script>    <script src="../../Scripts/jquery.unobtrusive-ajax.min.js"></script>    <script type="text/javascript">        if (window.parent.window != window) {            window.top.location.href = "/Login/Index";        }        function changeCheckCode() {            var newUrl = $("#img").attr("src") + 1;            $("#img").attr("src", newUrl);        }        function afterLogin(data) {            if (data != "ok") {                alert(data);                changeCheckCode();            } else {                window.location.href = "/Home/Index";            }        }    </script>    <style type="text/css">        *        {            padding: 0;            margin: 0;        }        body        {            text-align: center;            background: #4974A4;        }        #login        {            width: 740px;            margin: 0 auto;            font-size: 12px;        }        #loginlogo        {            width: 700px;            height: 100px;            overflow: hidden;            background: url('/Content/Images/login/logo.png') no-repeat;            margin-top: 50px;        }        #loginpanel        {            width: 729px;            position: relative;            height: 300px;        }        .panel-h        {            width: 729px;            height: 20px;            background: url('/Content/Images/login/panel-h.gif') no-repeat;            position: absolute;            top: 0px;            left: 0px;            z-index: 3;        }        .panel-f        {            width: 729px;            height: 13px;            background: url('/Content/Images/login/panel-f.gif') no-repeat;            position: absolute;            bottom: 0px;            left: 0px;            z-index: 3;        }        .panel-c        {            z-index: 2;            background: url('/Content/Images/login/panel-c.gif') repeat-y;            width: 729px;            height: 300px;        }        .panel-c-l        {            position: absolute;            left: 60px;            top: 40px;        }        .panel-c-r        {            position: absolute;            right: 20px;            top: 50px;            width: 222px;            line-height: 200%;            text-align: left;        }        .panel-c-l h3        {            color: #556A85;            margin-bottom: 10px;        }        .panel-c-l td        {            padding: 7px;        }        .login-text        {            height: 24px;            left: 24px;            border: 1px solid #e9e9e9;            background: #f9f9f9;        }        .login-text-focus        {            border: 1px solid #E6BF73;        }        .login-btn        {            width: 114px;            height: 29px;            color: #E9FFFF;            line-height: 29px;            background: url('/Content/Images/login/login-btn.gif') no-repeat;            border: none;            overflow: hidden;            cursor: pointer;        }        #txtUsername, #code, #txtPassword        {            width: 191px;        }        #logincopyright        {            text-align: center;            color: White;            margin-top: 50px;        }        a        {            color: Black;        }        a:hover        {            color: Red;            text-decoration: underline;        }    </style>    </head><body style="padding: 10px">        @using (Ajax.BeginForm("Login", "Logon", new AjaxOptions() { OnSuccess = "afterLogin" }))    {    <div id="login">        <div id="loginlogo">        </div>        <div id="loginpanel">            <div class="panel-h">            </div>            <div class="panel-c">                <div class="panel-c-l">                                    <table cellpadding="0" cellspacing="0">                        <tbody>                            <tr>                                <td align="left" colspan="2">                                    <h3>                                        后台管理系统账号登录</h3>                                </td>                            </tr>                            <tr>                                <td align="right">                                    账号:                                </td>                                <td align="left">                                    <input type="text" name="UName" value="admin" id="UName" class="login-text" />                                                                   </td>                            </tr>                            <tr>                                <td align="right">                                    密码:                                </td>                                <td align="left">                                    <input type="password" name="UPwd" id="UPwd" value="123" class="login-text" />                                </td>                            </tr>                            <tr>                                <td>                                    验证码:                                </td>                                <td align="left">                                    <input type="text" class="login-text" id="code" name="vCode" value="1" />                                </td>                            </tr>                            <tr>                                <td>                                </td>                            </tr>                            <tr>                                <td align="center" colspan="2">                                    <input type="submit" id="btnLogin" value="登录" class="login-btn" />                                </td>                            </tr>                        </tbody>                    </table>                </div>                <div class="panel-c-r">                    <p>                        请从左侧输入登录账号和密码登录</p>                    <p>                        如果遇到系统问题,请联系网络管理员。</p>                    <p>                        如果没有账号,请联系网站管理员。                    </p>                    <p>                        ......</p>                </div>            </div>            <div class="panel-f">            </div>        </div>        <div id="logincopyright">            Copyright ? 2013 itcast.com        </div>    </div>        }</body></html></span>

这样整个系统就写完了。


参考:

传智播客--互联网架构快餐之分布式缓存。


总结:

单机实现分布式,是大数据时代的要求,解决了高并发访问数据库死锁,实现了多客户端共享缓存。










1 0
原创粉丝点击