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

一、从单机到分布式


现在三台机器组成一个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>

这样整个系统就写完了。


参考:

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


总结:

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










郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。