罗布·霍华德
Microsoft Corporation
2001 年 4 月 26 日
在本月的专栏中,我们将介绍 ASP.NET 中的另一项新功能-缓存。 在 Beta 1 中,有两种不同的缓存功能 - 页面输出缓存和新的 缓存 对象。 ASP.NET 的 Beta 2 将引入更多缓存功能,我们将在 Beta 2 发布后在本专栏中讨论这些功能。
Caching
缓存是存储常用数据(通常是生成成本高昂的数据)以供重复使用的过程。 通常,此数据存储在内存中,因为从内存中检索数据比从其他位置(如数据库)检索数据要高效得多。
ASP.NET 有多个支持缓存的功能:用于存储任意数据的缓存 API 和用于存储频繁请求的页面的输出缓存。
让我们讨论一个快速示例。
电子商务网站上使用的目录每周只能更改一次。 可以生成一个 ASP.NET Web 应用程序,为该目录提供前端界面,使客户能够购买产品。 当客户只是浏览目录时,系统在大多数情况下 () 后端数据库服务器的网络调用。 数据库服务器还会对数据执行计算,例如联接查询和返回结果。
这种类型的配置非常常见,无论是目录还是来自数据库的某个其他类型的常见请求数据。 但是,上述示例中的设计可以改进。 我们知道数据库中的数据每周只更改一次,并且我们知道与检索数据相关的几个性能成本:
- 执行 ASP.NET 代码以发出数据库请求。
- 使用 Web 服务器的网络与数据库服务器通信。
- 在数据库服务器上完成的编译和执行查询 (或只是执行存储过程) 。
缓存使我们能够消除上述大部分工作,并提高应用程序的性能和可伸缩性。 我们可以通过缓存结果,并在每个请求 (静态提供结果,而不是动态地提供结果,) 并且可伸缩性会提高,因为我们使用更少的资源来为每个请求提供服务。
ASP.NET 的 Beta 1 引入了缓存 API 以及使用缓存 API 的页面输出缓存。 让我们更详细地讨论这两个功能。
缓存 API
在内存中存储频繁请求的数据对 ASP 开发人员来说并不是什么新鲜事。 我们有两种类型的对象可以解决此问题:
- 会话 对象
- 应用程序 对象
会话 用于跨多个请求存储每用户数据。 ASP.NET 中对 Session 进行了一些更改,但这些更改大多是应用程序级别的更改,不会影响 会话 的使用方式,也就是说,它仍然是一个简单的键/值对。
ASP 中的 Application 对象也会转发到 ASP.NET,它在函数 (键/值对) 保持不变。 例如,我们可以在 ASP 或 ASP.NET 中编写以下内容:
Application("SomeInterestingData") = "Example data"
Response.Write(Application("SomeInterestingData")
会话使用相同的语义 。We 只需命名一个键,在本例中,我们使用 SomeInterestingData,并为其赋值,在本例中为字符串 "Example data"。
ASP.NET 引入了另一个键/值对对象 - 缓存。 除了简单地存储键/值对外, 缓存 还添加了专为存储暂时性数据而设计的其他功能:
- 依赖项 - 添加到 缓存 中的密钥可以设置依赖项关系,以强制从 缓存中删除该密钥。 支持的依赖项包括密钥、文件和时间。
- 自动过期 - 添加到 缓存 中且没有依赖项的未充分利用的项如果未充分利用,它们将自动过期。
- 支持回调 - 可以添加代码路径,从缓存中删除项时将调用该路径,从而让我们有机会更新 缓存 或不删除该项。
当我们编写应用程序代码以使用 缓存 时,必须考虑一下:
在尝试使用该项之前,如果缓存中存在该项,请始终检查。
由于缓存将过期(基于依赖项或正在使用的项),因此,如果 缓存中不存在,则必须始终编写代码来创建或检索所需的项。
例如,如果我们编写了一个返回填充的数据集的函数:
Private Function LoadDataSet() As DataSet
  Dim sqlConnection As SQLConnection
  Dim sqlAdapater As SQLDataSetCommand
  Dim datasetProducts As New DataSet()
  Dim sqlDSN As String
  Dim sqlSelect As String
  ' Connection String and Select statement
  sqlDSN = "server=localhost;uid=<user id>;pwd=<password>;database=grocertogo"
  sqlSelect = "Select * From Products"
  ' Connect
  sqlConnection = new SQLConnection(sqlDSN)
  sqlAdapater = new SQLDataSetCommand(sqlSelect, sqlConnection)
  ' Fill dataset create product table
  sqlAdapter1.FillDataSet(datasetProducts, "products")
  Return products
End Function
我们可以轻松编写利用 缓存的代码。 仅当生成的数据集不在缓存中时,才执行 LoadDataSet () :
Public Function GetProductData() As DataSet
  If (IsNothing(Cache("ProductData")) Then
    Cache("ProductData") = LoadDataSet()
  Return Cache("ProductData")
End Function
但是,这与使用 应用程序并不完全不同。 我们基本上可以完成同样的事情。 当我们设置依赖项时,这很有趣。
缓存依赖项
依赖项允许我们根据对文件所做的更改、对其他缓存键的更改或在固定时间点使缓存中的特定项失效。 让我们看看其中每个依赖项。
基于文件的依赖项
当文件 (磁盘上的) 时,基于文件的依赖项会使特定 缓存 项失效。 例如,如果不是从数据库加载 ProductData,而是从 XML 文件加载它:
Dim dom As XmlDocument()
dom.Load(Server.MapPath("product.xml")
Cache("ProductData") = dom
如果 product.xml 发生更改,我们显然还需要使缓存中的数据失效。 假设 product.xml 与请求的应用程序位于同一目录中:
Dim dependency as new CacheDependency(Server.MapPath("product.xml"))
Cache.Insert("ProductData", dom, dependency)
在上面的代码示例中,我们将创建 CacheDependency 类的实例、依赖项,并传入product.xml文件的路径。 然后,我们使用缓存的 Insert () 方法创建 ProductData 密钥,该密钥依赖于它从中检索其数据的文件。
基于键的依赖项
当另一个缓存项发生更改时,基于键的依赖项会使特定缓存项失效。 例如,如果应用程序向缓存添加了多个数据集(如 ProductData、 SalesData和 MarketingData ), SalesData 并且 MarketingData 依赖于 ProductData 有效。 我们可以使用基于密钥的依赖项使 和 MarketingData (如果ProductData更改)失效SalesData。 在为 SalesData 和 MarketingData创建缓存条目时,我们将设置此依赖项:
Dim dependency (1) As String
dependencyKey(0) = "ProductData"
Dim productDataDependency As new CacheDependency(nothing, dependencyKey)
Cache.Insert("SalesData", LoadDataSet("Sales"), productDataDependency)
在上面的示例代码中,我们使用 Insert ()  方法创建一个名为 SalesData 的新 Cache 条目,该条目传入名为 的 CacheDependency 类的实例,该类名为 productDataDependency 缓存键 ProductData。 现在,每当 ProductData 更改时, SalesData 都会从缓存中删除。
基于时间的依赖项
基于时间的依赖项只是在定义的时间点使项过期。 同样,我们将使用 Cache 的 Insert () 方法创建此依赖项。 我们有两种基于时间的依赖项选项:
- 绝对 - 设置绝对时间;例如,缓存条目过期的当前时间 + 10 分钟。
- 滑动 - 重置缓存中的项在每次请求时过期的时间。
可以使用 “滑动 过期”选项缓存 ProductData 数据集,最长为 10 分钟。 只要在 10 分钟的时段内发出 请求 ProductData ,数据就可在另外 10 分钟内有效:
' 10 minute time span
Dim span As New TimeSpan(0,10,0)
Cache.Insert("ProductData", LoadDataSet(), nothing, nothing, span)
尽管这是对缓存 API 的相对轻量级的讨论,但希望你已经了解它是多么容易使用。 在底层,ASP.NET 利用缓存 API 和基于时间的过期策略来支持页面输出缓存的概念。 将来的一列将深入了解有关缓存 API 的更多详细信息。
页面输出缓存
页面输出缓存是 ASP.NET 的一项功能,允许将给定页面的全部内容存储在缓存中。 我们一直在使用上述示例中的缓存 API 在缓存中存储数据集。 如果我们确定可以缓存显示 DataSet) 结果的整个页面 (,而不是简单地存储数据集,该怎么办? 此处的好处是,我们不是在每个请求上动态执行 ASP.NET 页,而是从内存中静态提供它。 这可以带来巨大的性能提升!
有一个用于处理页面输出缓存的低级别和高级别 API。 我们只会在本文的余下部分讨论高级 API,但我们将在 beta 2) 发布后 (专栏中讨论低级别 API。
高级 API 包含一个 Page 指令,该指令指示 ASP.NET 缓存来自页面的一段时间的结果:
<%@ OutputCache Duration="10" %>
此指令添加到 ASP.NET 页顶部,只是指示 ASP.NET 缓存页面结果 10 秒。 10 秒后,页面将重新执行。 下面是使用此指令的示例:
<%@ OutputCache Duration="10" %>
<Script runat="server">
  Public Sub Page_Load()
    span1.InnerHtml = DateTime.Now.ToString("r")
  End Sub
</Script>
<font size=6>The time is: <font color=red><span id="span1" runat="server"/></font></font>
此页面只是在第一个请求上执行,显示请求的时间,然后从缓存提供 10 秒的时间。 例如,如果我们在 10:30:12 请求页面,则会在 10 秒内看到输出的该值,然后重新执行页面。
总结
缓存是我们 ASP 开发人员的一项新功能。 但是,它既易于使用又强大。 API 类似于应用程序和会话(一个简单的键/值对字典),但与应用程序或会话不同,缓存中的项可能会过期。 与应用程序不同,缓存支持基于文件、密钥和时间的依赖项以及回调,我们将保存这些内容供 Beta 2 后讨论使用。 缓存也由 ASP.NET 用于 ASP.NET 页输出缓存。 在将来的一个专栏中,我们将讨论 Beta 2 中将出现的一些新的缓存功能,例如部分页面缓存和 Web 服务缓存,我们还将介绍一些更高级的功能,如回调。
Rob Howard 是.NET Framework团队 ASP.NET 的项目经理。 他花任何业余时间,要么和家人在一起,要么在华盛顿东部钓鱼。