实际应用与集成
Andrea Colaci
Microsoft Windows Server AppFabric 以前的代号是“Velocity”,提供了可以集成到 Web 应用程序和桌面应用程序的分散式缓存。AppFabric 能够提高性能、可伸缩性和可用性,而从开发人员的角度来看,其行为方式与普通的内存缓存一样。任何可序列化的对象都可以缓存,例如 DataSet、DataTable、二进制数据、XML、自定义实体以及数据传输对象。
AppFabric 客户端 API 简单易用,服务器 API 则具备全功能的分布式资源管理器 (DRM),该工具可以管理一个或多个缓存服务器(多个服务器构成一个缓存群集)。每个服务器将提供各自的内存份额,执行各自的对象序列化与传输、区域分组、基于标签的搜索和过期关闭。缓存服务器还支持高可用性,借助该功能可在备用服务器上创建对象副本。
2009 年 6 月号的 MSDN 杂志 中包含一篇对 Windows Server AppFabric 的精彩介绍,作者是 Aaron Dunnington (msdn.microsoft.com/magazine/dd861287)。在本文中,我将说明如何将 AppFabric 缓存集成到桌面和 Web 应用程序中。与此同时,我会提供一些最佳实践,并针对如何利用 Microsoft .NET Framework 4 和 ASP.NET 4 中的新增功能给出一些提示。您还可以了解如何解决在使用分布式缓存时常见的问题。
接下来的所有代码示例均来自一个名为 Velocity Shop 的完整演示解决方案,该方案可以从 Codeplex 上获得,网址为:velocityshop.codeplex.com。
请注意,本文中将要讨论的 Windows Server AppFabric 不同于 Windows Azure 平台的 AppFabric。有关 Windows Azure 技术的更多信息,请参见 microsoft.com/windowsazure/appfabric。
入门
目前的 Windows Server AppFabric Beta 2 Refresh 可以通过几种方式来安装,以用于开发。借助 Web Platform Installer (microsoft.com/web/downloads),可以通过一个可配置的安装包轻松安装各种 Web 开发应用程序和框架。不仅如此,Web Platform Installer 还会进行更新,以便包括新发行的受支持的应用程序和框架。
如果只想安装 AppFabric,则可以访问 Windows Server 开发人员中心的 Windows Server AppFabric 页面,其中包含一个指向最新发行版的链接。该页面的网址为:msdn.microsoft.com/windowsserver/ee695849。
安装程序完成后,AppFabric 缓存差不多已经可以使用。下一步是创建一个命名缓存,即用于存储数据的逻辑容器。可以通过 Windows PowerShell 中的 New-Cache cmdlet 来执行此操作:
New-Cache -cacheName Catalog
若要开始在应用程序中使用 AppFabric 缓存,只需在 Visual Studio 项目中添加对 CacheBaseLibrary.dll、CASBase.dll、CASMain.dll 和 ClientLibrary.dll 的引用即可。
客户端库简单明了。以下代码显示了如何访问分布式缓存以便访问命名缓存,以及如何存储或检索对象:
cacheCluster = new DataCacheServerEndpoint[1];
cacheCluster[0] = new DataCacheServerEndpoint(
"ServerName", 22233, "DistributedCacheService");
DataCacheFactory factory =
new DataCacheFactory(cacheCluster, true, false);
DataCache cache = factory.GetCache("Catalog");
// Putting a product in cache
cache.Put("Product100", myProduct);
// Getting Product from cache
Product p = (Product)cache.Get("Product100");
在开始使用 AppFabric 缓存之前,最好是制定一些计划。第一步是考虑如何设置缓存。这决定了可以在应用程序中利用 AppFabric 缓存的哪些功能。
最开始,需要为您的项目设置一个命名缓存,如前所述。设置好后,可以自定义过期和通知策略。各个对象或集合可能需要不同的缓存持续时间,并且在内存过于紧张时合理地从缓存中删除(或者不删除)。若要为指定的命名缓存设置绝对过期超时时间,可使用带 TTL 参数的 New-Cache cmdlet。
除了命名缓存外,您可能还需要配置区域。这些区域就像缓存中的子组,可用来组织对象,从而简化在缓存中查找对象的过程。例如,我的应用程序使用一个消费电子设备目录。那么我可以创建一个名为“目录”的缓存,在其中将我的产品划分到名为“电视机”、“照相/摄像机”和“MP3 播放器”的区域中。您只能在运行时创建区域,为此,需要使用 dataCache.CreateRegion 方法,并提供区域名称:
// Always test if region exists;
try {
cache.CreateRegion("Televisions", false);
}
catch (DataCacheException dcex) {
// if region already exists it's ok,
// otherwise rethrow the exception
if (dcex.ErrorCode != DataCacheErrorCode.RegionAlreadyExists)
throw dcex;
}
请记住,如果已经存在同名的区域,将引发 DataCacheException 错误,因此您必须使用恰当的 try-catch 块。
但如果您需要根据功能来搜索产品呢?这时就可以使用 AppFabric 缓存的另一项有用功能:基于标签的搜索。该功能只在使用区域时可用,它可以为缓存中驻存的每项附加一个或多个标签,用于以后的搜索。
例如,我想找到“电视机”区域中带“LED 显示屏”和“46 英寸”标签的所有产品,那么我需要使用 GetByAllTags 方法,指定 DataCacheTags 列表,就这样。以下是一个基于标签的搜索示例:
DataCacheServerEndpoint[] cacheCluster = GetClusterEndpoints();
DataCacheFactory factory =
new DataCacheFactory(cacheCluster, true, false);
DataCache cache = factory.GetCache("Catalog");
IEnumerable<KeyValuePair<string, object>> itemsByTag =
cache.GetObjectsByTag(
new DataCacheTag("LED-Panel"), "Televisions");
在现有或新的应用程序中引入缓存层时,您还必须考虑到一些常见问题。这样有助于标识和区分可用于缓存的数据类型。以下就是三个类别:
- 引用数据,这包括不会经常发生变化的只读数据(如果有),例如国家/地区列表、一般储备项的目录或者产品数据表。
- 活动数据,这包括会根据每个用户或会话发生变化的数据,例如购物车或意向清单。
- 资源数据,这类信息常常会发生变化并且受多个用户活动类型的影响,例如产品库存在用户下订单后发生的变化。
这种分类有利于为每个命名缓存指定过期和通知策略,使您能够合理有效地利用资源。请记住,即便您可以向群集中添加缓存服务器,内存资源也始终是有限的。
缓存生命周期
当应用程序启动时,缓存是空的。但是用户要访问网站。那么如何准备好缓存呢?
由于使用直接缓存模式,因此当请求的数据不在缓存中时,启用缓存的应用程序必须能够切换到永久存储中,例如 SQL Server 数据库。但是在数据量很大的情况下,此过程的消耗也可能很大,尤其是在处理大量引用数据时,或者无法保证缓存加载的唯一性时。在某个特定的时候,可能会有多个线程针对某一对象检验缓存,随后尝试从存储中加载数据,并将数据驻存在缓存中用于以后的请求。由于既要提取未经缓存的数据,又要不必要地频繁执行数据缓存,因此会导致性能降低。
这时,类似于 IIS 7.5 服务自动启动之类的功能就派得上用场了。借助 AppFabric,您可以使用读取器锁来获得相似的结果。若要启用服务自动启动功能,必须对 applicationHost.config 进行一些更改,如下所示:
<serviceAutoStartProviders>
<add name="PrewarmMyApp"
type="MyWebApp.PreWarmUp, MyWebApp" />
</serviceAutoStartProviders>
接下来,使用自定义代码实现 Preload 方法,将引用数据和资源数据载入命名缓存中:
using System.Web.Hosting;
namespace DemoApp {
public class WarmUp : IProcessHostPreloadClient {
public void Preload(string[] parameters) {
// Cache preload code here...
}
}
}
这使 Web 应用程序仅在 Preload 方法执行完之后可用。
在如图 1 所示的服务器场方案中,还有另外一个问题:每个冷启动的应用程序需要访问永久存储以加载其缓存。默认的 ASP.NET 缓存绑定到用来运行应用程序的 appDomain,因此您只执行一次缓存加载操作。但在 AppFabric 中,缓存可以在各个 Web 服务器之间共享,从而在各个 Web 应用程序之间共享,因此能避免并行的缓存加载尝试。
图 1 服务器场中的 AppFabric 缓存
将缓存加载委托给单个服务器有多种选择。AppFabric Beta 1 中引入了读取器锁这种功能。借助该功能,您可以在缓存项密钥添加到缓存之前将其锁定。只有第一个锁定密钥的预加载代码才能启动与其关联的数据加载。这就像在缓存加载期间,在使用密钥之前预定密钥。您还可以利用该技术在各个 Web 服务器之间分发缓存加载操作。您可以预先对每个服务器进行配置,使其加载与特定的预定密钥关联的数据。
了解何时加载缓存是一个常见的与分布式资源(如缓存群集)相关的同步问题。利用 AppFabric 缓存,可以通过几种方法来确定缓存何时就绪。一种方法是让服务器在加载各自预定的密钥数据之后轮询公共密钥。另外的方法包括,订阅缓存就绪通知,甚至可以在一个单独的服务或应用程序中执行预加载阶段,这是因为缓存现在是分布式的,从 Web 和桌面应用程序中都可以访问。
另外,还可以利用 .NET Framework 4 的 System.Threading.Tasks.Parallel 类来并行执行多个缓存加载操作,如图 2 所示。
图 2 并行的缓存加载
// load catalog items from DB
SQLServerCatalogProvider SQLServerCatalogProvider =
new SQLServerCatalogProvider();
itemsInCategory =
SQLServerCatalogProvider.GetItemsByCategoryId(categoryId);
_helper.CreateRegion(categoryId);
Parallel.ForEach(itemsInCategory, item =>{
// put each catalog item in cache with tags
if (item.Tags==string.Empty)
_helper.PutInCache(item.ProductId, item, categoryId);
else
_helper.PutInCache(item.ProductId, item, categoryId, item.Tags);
});
// Code from Helper class
public void PutInCache(string key, object value,
string region, string tags) {
List<DataCacheTag> itemTags = new List<DataCacheTag>();
foreach (var t in tags.Split(',').ToList())
itemTags.Add(new DataCacheTag(t.ToLower()));
_cache.Put(key, value, itemTags , region);
}
在以后的 AppFabric 缓存版本中,计划增加缓存遍历功能。这样当数据不存在时,您就能够自动运行自定义代码来加载缓存;与此相反,当缓存中的信息已更新时,就将数据保存到永久存储中。
ASP.NET 集成
借助 ASP.NET 提供程序模型,开发人员可以从三种会话提供程序中进行选择:InProc、StateServer 和 SQLServer。使用 AppFabric 缓存时,第四个会话提供程序在技术上是可行的,但是应当小心不要将会话与缓存混淆。缓存用于提高性能,会话用于使应用程序达到一定的状态。
用于 ASP.NET 的 AppFabric 缓存会话提供程序使用其分布式缓存(可能具有高可用性)作为 ASP.NET 会话的存储库。这一功能是透明的,且无需打断现有的代码便可以使用。有了这样的提供程序,ASP.NET 会话就可以在 Web 服务器崩溃或脱机的情况下保持可用,这是因为,会话是另外存储在 AppFabric 缓存中的。
安装并配置完 AppFabric 缓存之后,您必须创建一个命名缓存来存储 ASP.NET 会话。然后就可以通过修改 Web.config 来启用 DataCacheSessionStoreProvider,如图 3 所示。
图 3 在 AppFabric 缓存中启用 ASP.NET 会话
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="dataCacheClient"
type="Microsoft.Data.Caching.DataCacheClientSection, CacheBaseLibrary"
allowLocation="true" allowDefinition="Everywhere"/>
<section name="fabric"
type="System.Data.Fabric.Common.ConfigFile, FabricCommon"
allowLocation="true" allowDefinition="Everywhere"/>
<!-- Velocity 1 of 3 END -->
</configSections>
<dataCacheClient deployment="routing">
<localCache isEnabled="false"/>
<hosts>
<!--List of services -->
<host name="localhost" cachePort="22233"
cacheHostName="DistributedCacheService"/>
</hosts>
</dataCacheClient>
<fabric>
<section name="logging" path="">
<collection name="sinks" collectionType="list">
<!--LOG SINK CONFIGURATION-->
<!--defaultLevel values: -1=no tracing;
0=Errors only;
1=Warnings and Errors only;
2=Information, Warnings and Errors;
3=Verbose (all event information)-->
<customType
className="System.Data.Fabric.Common.EventLogger,FabricCommon"
sinkName="System.Data.Fabric.Common.ConsoleSink,FabricCommon"
sinkParam="" defaultLevel="-1"/>
<customType
className="System.Data.Fabric.Common.EventLogger,FabricCommon"
sinkName="System.Data.Fabric.Common.FileEventSink,FabricCommon"
sinkParam="DcacheLog/dd-hh-mm" defaultLevel="-1"/>
<customType
className="System.Data.Fabric.Common.EventLogger,FabricCommon"
sinkName="Microsoft.Data.Caching.ETWSink, CacheBaseLibrary"
sinkParam="" defaultLevel="-1"/>
</collection>
</section>
</fabric>
<appSettings/>
<connectionStrings/>
<system.web>
<sessionState mode="Custom" customProvider="Velocity">
<providers>
<add name="Velocity"
type="Microsoft.Data.Caching.DataCacheSessionStoreProvider, ClientLibrary"
cacheName="session"/>
</providers>
</sessionState>
...
由于只有服务器一端需要使用 .NET Framework 4,而客户端库使用 .NET Framework 3.5 和 4 均可,因此您可以将 AppFabric 缓存集成到现有的 ASP.NET 3.5 应用程序中。
ASP.NET 4 的另一个扩展点是输出缓存。从 ASP.NET 1.0 版本以来,已经能够将生成的页面和控件输出存储在内存缓存中。后续请求可以从内存中获取此输出而不用再次生成。ASP.NET 4 支持配置一个或多个自定义输出缓存提供程序。ASP.NET 4 后将会提供 AppFabric 输出缓存提供程序,您也可以通过扩展 OutputCacheProvider 类和修改 Web.config 来自行启用。
ORM 集成
多数常用的对象关系映射 (ORM) 框架具备名为二级缓存的功能。二级缓存是一个存储库,用于在一段可配置的时间内存储实体、集合和查询结果。如果要从永久存储中加载某个实体,ORM 会首先检验二级缓存中是否已经加载了该实体。如果已经存在,则会向调用代码传递一个所请求实体的实例,而不需要直接访问数据库。
每个 ORM 实现都有各自用来处理实体关联、集合与查询结果的策略,这对二级缓存也同样适用。根据您所使用的 ORM,您可能会发现自己的二级缓存自定义和消耗选项是有限的,这迫使您不得不采用特殊的方法来消耗缓存更改。例如,如果过期策略和缓存依赖关系难以自定义,则对象争用情况可能会增加,而缓存效率会降低。
NHibernate 和实体框架都可以使用 AppFabric 缓存作为二级缓存。Nhibernate 通过 nhibernate.caches.velocity 来启用此功能 (sourceforge.net/projects/nhcontrib/files/NHibernate.Caches/)。实体框架通过由 Jaroslaw Kowalski 提供的 EFCachingProvider 来启用此功能 (code.msdn.microsoft.com/EFProviderWrappers)。
开始运行 AppFabric
正如您所看到的,利用 Windows Server AppFabric 缓存可以轻松地在任何新的或现有的应用程序中启用群集级别的缓存。若要充分发挥缓存的优势,您需要标识适当的候选数据对象(您可能已经对本地服务器缓存执行了这一操作)。此外,还需要定义过期和通知策略。
AppFabric 在 Windows Server 2008 许可证中提供,也可以从以下网址下载:msdn.microsoft.com/windowsserver/ee695849。它还在 Windows Azure 中作为“缓存”角色存在。
下载 AppFabric,设置单/多服务器缓存群集;如果您有兴趣,下载并在 CodePlex 上运行 Velocity Shop,以快速评估和体验该解决方案内演示的所有 AppFabric 功能。
Andrea Colaci* 是一名有 10 年以上经验的顾问和培训师。他具有强烈的求知欲,渴望了解最新的语言和开发工具。*
衷心感谢以下技术专家审阅本文:Scott Hanselman