原文地址:ASP.NET MVC Tip #39 – Use the Velocity Distributed Cache
原文作者:Stephen Walther
本文地址:[翻译]ASP.NET MVC Tip #39 – 在ASP.NET MVC中使用分布式缓存 译者:紫色永恒
在这篇文章中,我将带您体验如何使用Velocity分布式缓存提高ASP.NET MVC应用程序性能。同时我也会向您说明如何使用Velocity作为会话状态提供者。
使用缓存是提高ASP.NET MVC应用程序性能的最好方式。我们知道,在ASP.NET MVC中最耗时的操作非数据库的读取莫数,那么,尽量的避免读取数据库显然成为了提升性能最好的办法。缓存可以将经常访问的数据保持在内存中从而大幅减少数据库的读取。
由于ASP.NET MVC是ASP.NET框架的一部分,这使得我们在ASP.NET MVC中仍然可以使用标准的System.Web.Caching.Cache对象。标准的ASP.NET缓存对象很好很强大,我们可以通过它指定某些数据在特定的时间内被缓存,建立缓存项与文件系统或者数据库表之间的依赖,甚至可以在不同的缓存向之间建立复杂的依赖链。也就是说,我们可以通过ASP.NET缓存对象作很多很多很多…的事情。
但是标准的ASP.NET缓存对象的也有一些限制,比如它只能和我们的web应用程序运行在同一个进程,也就是说它并不是分布式的。不同应用程序间的缓存并不能共享,我们只能为每个应用程序构造相同的缓存。
在单一服务器架构下,传统的ASP.NET缓存在web应用程序中工作得非常好。但是当我们想使用服务器组等集群架构时,问题就出现了。比如我们有一个需要处理成十亿上百亿用户访问的web应用程序(译者按:这有点夸张吧…),或者当我们的服务器出现故障时我们并不想重新从缓存加载数据。当我们欲在多组web服务器上共享缓存是,分布式缓存应运而生。
在这篇文章中我将向您解释如何在ASP.NET MVC应用程序中使用代号为Velocity的微软分布式缓存策略。建立和使用Velocity十分的简单,从标准的ASP.NET缓存转换至Velocity分布式缓存也不会使您痛苦万分。
同时我也会说明如何将Velocity作为会话状态提供者(session state provider).Velocity允许您在MVC应用程序服务器群组中使用ASP.NET会话状态。您可以通过Velocity在分布式缓存中存储会话状态。
Velocity的安装和配置
当你看到这篇文章的时候,Velocity和ASP.NET MVC一样还没有正式发布,仍然处于Preview的状态。不过你现在就可以下载Velocity来一个提前体验。
下面的地址中包括所有有关Velocity相关信息。
http://msdn.microsoft.com/en-us/data/cc655792.aspx
您可以通过这个页面的链接下载Velocity。
安装Velocity十分简单,您需要将它安装在您想使用分布式缓存的服务器上(缓存服务器),缓存会从这些服务器上自动的组织起来。
在将Velocity安装在缓存服务器上之前,您需要在您的网络中创建一个共享以便所有的缓存服务器都能够访问它。这个文件共享将包括缓存配置文件(ClusterConfig.xml)。这个文件是一个标准的XML文件,它用来对分布式缓存进行相关配置。
在Beta版中,您必须在这个文件共享上给予Everyone帐号读写权限。右键单击文件夹,选择属性菜单然后选择安全选项单。点击编辑按钮,选择添加,输入Everyone然后点击OK即可。这里要确认Everyone帐号具有读写权限。
当您运行Velocity(图1)的安装程序时,您将被要求填入如下信息:
· Cluster Configuration Share – 这里的路径指向您刚刚建立的文件共享.
· Cluster Name – 组名称. 按照您的意愿起个名字吧 (比如, MyCacheCluster).
· Cluster Size – 您期望在这个组中使用的缓存服务器的数量.
· Service Port Number – 应用程序和缓存服务器通信的端口(注意如果您有防火墙,您需要在防火墙中解除对该端口的屏蔽)
· Cluster Port Number -- 缓存服务器之间通信的端口。Velocity将使用这个端口号。(同样需要在防火墙中解除屏蔽)
· Max Server Memory – 允许Velocity在这台服务器上使用的内存最大值。
图 1 – 安装 Velocity
您必须在每台安装Velocity后的服务器上配置防火墙的例外规则,否则Velocity的通讯将被阻止。您可以为DsitributedCache.exe进程或者为每个端口号(22233端口,22234端口和22235端口等)建立一个防火墙例外规则。
使用 Velocity 管理工具
您可以通过Velocity Administration Tool命令行工具管理Velocity(图2)
图2 2 – Velocity 管理工具
Velocity管理工具支持如下命令(这些命令十分有用):
· start cluster – 为组中的每一台缓存服务器启动Velocity。
· stop cluster – 与上一条正相反。
· create cache – 建立并命名一个新缓存。
· delete cache – 删除一个已存在的缓存。
· list host – 列出组中的所有缓存服务器
· list cache – 类出组中的所有缓存配置。
· show hoststats <cache server>:<cache port>– 列出指定缓存服务器上的统计信息。
安装Velocity后您需要做的第一件事就是使用如下命令启动缓存组:
start cluster
在ASP.NET MVC应用程序中使用Velocity
完成Velocity的建立和启动后,您可以建立一个使用Velocity缓存的ASP.NET MVC应用程序。为了在项目中使用Velocity,您需要添加对如下程序集的引用:
· CacheBaseLibrary.dll
· ClientLibrary.dll
您可以在Program Files\Microsoft Distributed Cache文件夹或任意一台安装了Velocity的机器上找到这些dll.您也可以将这些dll从缓存服务器上拷贝到您的开发环境中。
同时,您需要修改您的ASP.NET MVC应用程序的web.config文件。在<configSections>节点中添加如下子节点:
-
<section name="dcacheClient"
- type="System.Configuration.IgnoreSectionHandler"
- allowLocation="true" allowDefinition="Everywhere"/>
<section name="dcacheClient"
type="System.Configuration.IgnoreSectionHandler"
allowLocation="true" allowDefinition="Everywhere"/>
接下来,在配置文件的任意位置添加如下节点:
-
<dcacheClient deployment="simple" localCache="false">
-
<hosts>
-
- <host name="localhost"
- cachePort="22233"
- cacheHostName="DistributedCacheService" />
- </hosts>
-
</dcacheClient>
<dcacheClient deployment="simple" localCache="false">
<hosts>
<!--List of hosts -->
<host name="localhost"
cachePort="22233"
cacheHostName="DistributedCacheService" />
</hosts>
</dcacheClient>
在这里您可能需要改动一下host节点下name为“localhost”的值。换句话说,MVC应用将在本地使用缓存服务。如果缓存服务器不在您的本地环境中,您需要更改这个值,以使它指向正确的服务器。
增加缓存项及从缓存中检索指定项
分布式缓存的使用方式和普通的ASP.NET缓存使用方式十分相似。您可以使用如下的方法在分布式缓存中增加,获取和移除缓存项:
· Add() – 向分布式缓存中添加一项.如果此项的键已经存在,那么将会抛出一个异常。
· Get() – 按照指定的键从分布式缓存中获取缓存项。
· Put () – 向分布式缓存中添加一项.如果此项的键已经存在,那么它将会被替换。
· Remove() – 从分布式缓存中移除一个存在的缓存项。
让我们看看示例,代码段1中的controller使用分布式缓存来缓存影视数据库中的记录。
代码段 1 – HomeController.cs
-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Web;
-
using System.Web.Mvc;
-
using System.Data.Caching;
-
using Tip39.Models;
-
using System.Diagnostics;
-
using System.Web.Configuration;
-
using System.Web.Hosting;
-
using System.Data.Linq.Mapping;
-
using System.Data.Linq;
-
-
namespace Tip39.Controllers
- {
- [HandleError]
- public class HomeController : Controller
- {
- private DataContext _dataContext;
- private Table<Movie> _table;
-
- public HomeController()
- {
-
- var conString = WebConfigurationManager.ConnectionStrings["Movies"].ConnectionString;
-
-
- var url = HostingEnvironment.MapPath("~/Models/Movie.xml");
- var xmlMap = XmlMappingSource.FromUrl(url);
-
-
- _dataContext = new DataContext(conString, xmlMap);
- _table = _dataContext.GetTable<Movie>();
- }
-
-
- public ActionResult Index()
- {
-
- var factory = new CacheFactory();
- var cache = factory.GetCache("default");
- var movies = (List<Movie>)cache.Get("movies");
-
-
- if (movies != null)
- {
- Debug.WriteLine("Got movies from cache");
- }
- else
- {
- movies = (from m in _table select m).ToList();
- cache.Put("movies", movies);
- Debug.WriteLine("Got movies from db");
- }
-
-
- return View("Index", movies);
- }
-
- }
- }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.Caching;
using Tip39.Models;
using System.Diagnostics;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Data.Linq.Mapping;
using System.Data.Linq;
namespace Tip39.Controllers
{
[HandleError]
public class HomeController : Controller
{
private DataContext _dataContext;
private Table<Movie> _table;
public HomeController()
{
// Get connection string
var conString = WebConfigurationManager.ConnectionStrings["Movies"].ConnectionString;
// Get XML mapping source
var url = HostingEnvironment.MapPath("~/Models/Movie.xml");
var xmlMap = XmlMappingSource.FromUrl(url);
// Create data context
_dataContext = new DataContext(conString, xmlMap);
_table = _dataContext.GetTable<Movie>();
}
public ActionResult Index()
{
// Try to get movies from cache
var factory = new CacheFactory();
var cache = factory.GetCache("default");
var movies = (List<Movie>)cache.Get("movies");
// If fail, get movies from db
if (movies != null)
{
Debug.WriteLine("Got movies from cache");
}
else
{
movies = (from m in _table select m).ToList();
cache.Put("movies", movies);
Debug.WriteLine("Got movies from db");
}
// Display movies in view
return View("Index", movies);
}
}
}
Index()方法返回数据库中所有的行。首先,这个方法试图从缓存中获取数据,如果获取失败,则从数据库中获取数据并在缓存中添加一个缓存项。
Index()方法点用Debug.WriteLine()来向Visual Studio控制台窗口显示消息。无论数据是从缓存还是从数据库取出的,控制台窗口都会忠实的体现出来。(图2)
图 2 – 使用Visual Studio 控制台跟踪缓存行为
您也可以使用Velocity管理工具监视分布式缓存。执行如下命令来显示分布式缓存的统计信息:
show hoststats server name:22233
将“server name”替换成您的缓存服务器的名称(很可惜,在这里不能使用“localhost”)。执行这条命令后,您将得到有关分布式缓存非常全面的统计信息,如缓存项的数量,缓存大小,缓存被调用的次数等等(图3)。
图 3 – 缓存统计
代码段1中的Index()方法首先实例化了一个CacheFactory类。这个类用来从分布式缓存中提取特定的缓存项。
Velocity可以管理不同名称的缓存。您可以将不同的数据通过不同的缓存组织起来。想象一下,每个具有特定名字的缓存就像一个分布式数据库。如果您是第一次安装Velocity,您会得到一个默认的叫做“default”的缓存项。在代码段1中,CacheFactory就是用来从获取这个被命名为“default”的缓存项的。
接着,Cache.Get()方法用来从从缓存中获取电影数据库中的记录。当Index()方法第一次被执行时,Get()方法不会返回任何值。因为分布式缓存中并不存在任何数据。
如果Get()方法从缓存中获取数据记录失败,则会真正的从数据库中获取记录。然后这些记录在Put()方法的帮助下添加到分布式缓存中。
这里要注意一下,利用缓存存储或者获取的数据都是无类型的对象。在您使用这些对象之前,您必须将它们转换为特定的类型。典型的情况是比如您将分布式缓存应用于产品信息,您需要建立一个强类型的产品信息类。
另外您还需要注意的是,当Visual Studio项目重新启动或编译后分布式缓存仍然有效。如果您需要在开发时随时清除缓存,请在Velocity管理工具中使用这些命令
stop cluster
start cluster
我们可以缓存哪些数据?
您可以将任何可序列化的类型添加到缓存中。也就是说您可以将所有标记了[Serializable]特性的类型添加到缓存中去。(这个类所有依赖的类型也必须是可序列化的) 代码段1中的Home controller使用LINQ to SQL来从数据库中获取数据记录。当在分布式缓存中应用LINQ to SQL时您必须注意,取决于您创建LINQ to SQL类的方式,这列类有可能不允许被序列化。
如果您使用对象关系设计器建立您的LINQ to SQL类则将导致所有的类型不可以被序列化。为了解决这个问题,我选择手动创建LINQ to SQL类。请看代码段2中的Movie.cs类。
代码段 2 – Models\Movie.cs
-
using System;
-
-
namespace Tip39.Models
- {
- [Serializable]
- public class Movie
- {
- public int Id { get; set; }
- public string Title { get; set; }
- public string Director { get; set; }
- public DateTime DateReleased { get; set; }
- }
- }
using System;
namespace Tip39.Models
{
[Serializable]
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public string Director { get; set; }
public DateTime DateReleased { get; set; }
}
}
注意,Movie类被包含[Serializable]特性
代码段3中的XML mapping文件被用于HomeController被构造时初始化LINQ to SQL数据上下文。这个XML mapping文件将Movie类和它的属性映射到Movies表和字段。
代码段 3 – Models\Movie.xml
-
<?xml version="1.0" encoding="utf-8" ?>
-
<Database Name="MoviesDB" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
-
<Table Name="Movies" Member="Tip39.Models.Movie">
- <Type Name="Tip39.Models.Movie">
- <Column Name="Id" Member="Id" IsPrimaryKey="true" IsDbGenerated="true"/>
- <Column Name="Title" Member="Title" />
- <Column Name="Director" Member="Director" />
- <Column Name="DateReleased" Member="DateReleased" />
- </Type>
-
</Table>
-
</Database>
<?xml version="1.0" encoding="utf-8" ?>
<Database Name="MoviesDB" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="Movies" Member="Tip39.Models.Movie">
<Type Name="Tip39.Models.Movie">
<Column Name="Id" Member="Id" IsPrimaryKey="true" IsDbGenerated="true"/>
<Column Name="Title" Member="Title" />
<Column Name="Director" Member="Director" />
<Column Name="DateReleased" Member="DateReleased" />
</Type>
</Table>
</Database>
在分布式缓存中使用LINQ to SQL还有另外一点需要注意。您应该清楚一个LINQ to SQL请求直到您真正使用其查询结果时才会真正的执行。确认您向缓存中添加的是LINQ to SQL请求的结果而不是请求本身。
代码段1中的Index()方法将电影记录添加到缓存中,如下
- movies = (from m in _table select m).ToList();
- cache.Put("movies", movies);
movies = (from m in _table select m).ToList();
cache.Put("movies", movies);
注意LINQ to SQL中调用的ToList()方法,它使得请求真正被执行并且将数据添加到电影实体中。否则,它只是个LINQ to SQL请求表达式而已。
将Velocity作为会话状态提供者
默认情况下,ASP.NET框架将会话状态储存在与您的ASP.NET MVC应用程序相同的进程中,这种方式提供了最卓越的性能。然而这种方式有一个很大的缺点:您无法再服务器集群中应用会话状态。
ASP.NET框架提供两种改变在进程中存储会话状态的方式。您可以将会话状态信息储存在您网络中的状态服务器上或者将它们储存在SQL Server数据库中。这两种方式都可以让您实现建立一个会话状态服务中心。得益于此,您可以很好的在服务器集群中控制ASP.NET MVC应用。
不过无论是将会话状态储存在状态服务器上还是SQL Server数据库中,这里都存在一些缺点。其一,使用状态服务器的方式容错性不是很好;其二,使用SQL Server的话,则速度不会很快(您的MVC应用中用户的每个请求都将导致执行一个从数据库中读取会话状态的操作)。
Velocity为您提供了一种更好的进程外存储会话状态的方法。您可以将会话状态信息存储在Velocity分布式缓存当中。而无论是容错性还是相应速度,都要比以上说的两种方法好的多。
假如您想计算并保持一个特定用户访问页面的次数(图4),您可以将访问次数存储在会话状态中。代码段4中的Counter controller将访问次数保存在以“count”命名的会话状态中。图 4 -- 计算页面访问次数
代码段 4 – CounterController.cs
-
using System.Web.Mvc;
-
-
namespace Tip39.Controllers
- {
- public class CounterController : Controller
- {
- public ActionResult Index()
- {
- var count = (int?)Session["count"] ?? 0;
- count++;
- Session["count"] = count;
- return View("Index", count);
- }
- }
- }
using System.Web.Mvc;
namespace Tip39.Controllers
{
public class CounterController : Controller
{
public ActionResult Index()
{
var count = (int?)Session["count"] ?? 0;
count++;
Session["count"] = count;
return View("Index", count);
}
}
}
默认情况下,Counter controller会将会话状态存储在进程内。如果您希望使用Velocity来存储会话状态的话,您需要修改web.config文件。将<sessionState>信息添加到<system.web>节点中,如代码段5
代码段 5 – Session Configuration for Velocity
-
<sessionState mode="Custom" customProvider="dcache">
-
<providers>
- <add
- name="dcache"
- type="System.Data.Caching.SessionStoreProvider"/>
-
</providers>
-
</sessionState>
<sessionState mode="Custom" customProvider="dcache">
<providers>
<add
name="dcache"
type="System.Data.Caching.SessionStoreProvider"/>
</providers>
</sessionState>
这样您的会话状态就会被储存在分布式缓存当中了。您可以通过Velocity管理工具确认Counter controller是否使用了分布式缓存。执行下面的命令,结果会显示出分布式缓存被访问的次数:
show hoststats server name:22233
Counter controller的Index()方法每执行一次,总的请求统计将会增加两次:一次是读取会话状态、一次是写入会话状态。看图5
图 5 – 查看总请求数
需要注意的是,您根本不用改动现有程序中的任何代码就可以将会话状态存储在Velocity分布式缓存中。您可以通过修改web.config来更改会话状态的存储方式。这意味着您可以随时根据需要在几种存储方式中自由转换。
总结
在这篇文章中,我简短的向您介绍了Velocity分布式缓存,并示范了如何在ASP.NET MVC应用程序中通过分布式缓存在多请求的情况下存储和获取数据。另外我还向您展示了如何通过Velocity分布式缓存存储会话状态的种种优点。
当然,在这里我仅仅是为您说明了Velocity的最基本的应用。Velocity还有很多的特点我在这篇文章并没有谈及。比如Velocity提供了一个锁定机制。您可以选择使用乐观或悲观锁定来防止并发攻击。您还可以使用Velocity来标记和搜索缓存中的缓存项。
尽管这只是一个简短的介绍,但我仍希望我已经向您提供了足够的细节来帮助您尝试在ASP.NET MVC应用程序中使用Velocity。
|
请发表评论