分布式缓存--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>
这样整个系统就写完了。
参考:
传智播客--互联网架构快餐之分布式缓存。
总结:
单机实现分布式,是大数据时代的要求,解决了高并发访问数据库死锁,实现了多客户端共享缓存。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。