ASP.Net MVC4+Memcached+CodeFirst实现分布式缓存
part 1:给我点时间,允许我感慨一下2016年
正好有时间,总结一下最近使用的一些技术,也算是为2016年画上一个完美的句号,回顾2016年,感受颇多,感恩那些帮助我的人。展望2017年,我相信一定会遇到一个更好的自己。附上自己喜欢的一张图片:
好了~~~装逼结束,下面开始说说如何实现分布式缓存在项目中的应用。
part2:先分析以下需求
软件架构从单机到分布式遇到的问题(当然这是一个很深的问题,由于能力有限今天就说说如何实现用户信息的分布式存储的问题),也就是:走向分布式第一步:多台机器共享用户登录状态,该如何实现?例如:现在有三台机器组成了一个web的应用集群,其中一台机器用户登录,然后其他另外两台机器共享登录状态?具体请看下面的图示:
问题:如果有一个用户第一次登陆的时候,负载均衡把该用户分配到IIS1这台服务器上,该用户的信息就会被保留到IIS1这台服务器上,但是如果该用户再次访问其他的web资源的时候,被分配到IIS2上,这是IIS2中,没有改用户的信息,会发生什么?该怎么解决?该选用什么介质来保存状态比较合适?
从图中可以看出就是保存对应用户的信息,可能有人会用下面的几种方法:1)直接保存到进程内session中;2)使用ASP.Net进程外session;3)用数据库存储当前登录状态;4)微软的状态服务器
当然了,使用上面的三种方法不是不可以,但是从网站的整体性能上考虑,确实不太完美,影响系统性能。下面来一一分析这三种方法的缺点:
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,如下:
1、首先准备好工具类:
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 }
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));
}
}
}
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");
}
}
}
2、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; }
}
}
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; }
}
}
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; }
}
}
3、接下来的代码是使用分布式缓存中最关键的一点:
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");
}
}
}
到这里利用ASP.Net MVC4、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
如果觉得好的话,希望大家推荐一下哈~~~,后期还有更新。希望大家可以交流交流,这样都有进步,不要再留邮箱哦
注:转载请注明作者,谢谢。
来源:oschina
链接:https://my.oschina.net/u/4332587/blog/4000151