65.9K
CodeProject 正在变化。 阅读更多。
Home

Pfz.Caching - 使用 ViewId 而非 ViewState

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (12投票s)

2009年10月7日

CPOL

8分钟阅读

viewsIcon

44953

downloadIcon

269

用于缓存数据的框架,包括将 ViewStates 存储在文件中的可能性,重用相同的文件以避免过多的硬盘使用。

引言

这是一个用于通用缓存可序列化对象的框架。它有一个通用的 Cache 类,可以在内存/磁盘中缓存任何对象。如果对象不再内存中,它将从磁盘读取。

它还有一个 CacheDictionary ,功能相同,但对小数据有一些优化,避免生成过多的“缓冲区文件”。

最后,最棒的部分来了:在项目中添加 App_Browsers ,您可以让所有 ViewStates 使用这项技术,这样只有很小的 ViewId 会被发送到客户端,而不是完整的 ViewState。此外,它会在一段时间后删除未使用的文件,并重用相同的文件,避免浪费硬盘空间。

背景

我在 Web 开发中并没有遇到太多问题。我开始编程时,电脑只有 1MB 或 2MB 内存。但是,我看到人们普遍对何时存储数据感到困惑。关于 ViewState 和 Session 的文档也很令人困惑,它们看起来可以互换,但实际上并非如此。

我首先创建了一个用于分页记录集的缓存技术。

然后,我创建了用于任何数据的缓存技术。

最后,是用于 ViewStates 的模块。它非常棒,并且已经稳定运行一年多了。

工作原理(基础)

实现可能非常复杂,因为代码是线程安全的,并且您需要正确管理垃圾回收,但原理非常简单。

  1. 每个“缓冲区”对象都会被序列化,并为其缓冲区创建一个哈希码(校验和)。如果该缓冲区的目录存在,我将检查是否有具有相同长度的缓冲区,如果有,并且是相同的,则避免创建新的缓冲区,或者我创建一个新的唯一 ID,保存文件并返回该缓冲区的 ID 和哈希码。这在所有 Session 中共享,但由于数据是只读的,所以没有问题。
  2. 项 1 是 BufferId,而不是 ViewId。现在,有了 bufferId,我将尝试查找当前 Session 中是否存在包含该 bufferId 的内存中的 ViewState 信息。如果没有,将生成一个新的 ViewId。如果存在,将返回旧的 ID。显然,每次重用文件时,我都会执行一次 keep-alive(在文件上更新文件的日期/时间,在内存中调用 GCUtils.KeepAlive)。
  3. 每个缓冲区也使用弱引用保存在内存中,但为了防止最近使用的缓冲区被回收,我调用 GCUtils.KeepAliveGCUtils.KeepAlive (而不是 GC.KeepAlive)保证对象将在下一次垃圾回收中存活。
  4. ViewId 是为 sessionId 创建的,所以不会出现一个用户获取另一个用户 viewstate 的问题,即使内部缓冲区相同(在这种情况下,每个用户将有不同的 ViewId,但它们将指向同一个缓冲区)。此外,每个页面都会生成一个新的 viewstate,所以我将尝试获取现有 viewstate 的 ID(如果可能),或者创建一个新文件。
  5. 默认情况下,FileCachePersister 每 30 分钟运行一次清理进程,删除超过 4 小时的文件。这与 Session 过期时间无关。

工作原理(高级)

此时,我不会详细解释 CacheDictionary (它实际上是 Cache 对象的字典,但有一些优化)的细节,也不会解释 WeakDictionary,因为其本身的解释会比整篇文章还长。但重要的是要知道,它是一个允许垃圾回收器收集其项目的字典,但会保持最近使用的值存活。
我将开始解释 CacheManagerCacheManager 类负责加载和保存缓冲区(字节),因为它不了解对象的实际类型。最重要的东西是一个 WeakDictionary,其中键是缓冲区的哈希码,值是 ID 和序列化字节的字典。其内部函数尝试使用哈希码和缓冲区 ID 在内存中查找值,如果没有找到,则请求持久化器加载它,然后存储加载的值(如果有),并对其进行 KeepAliveSave 函数执行类似的过程,尝试在内存中查找兼容的缓冲区,以重用 ID,或者如果没有找到,则调用持久化器保存并返回生成的 ID。

通用的 Cache 类具有序列化和反序列化缓冲区的能力。它使用 CacheManager 来读取或写入这些缓冲区,但 Cache 本身有自己的 WeakReference 指向实际对象。这样做是因为,在反序列化缓存对象时,只需要对象的 ID,而不是实际对象。如果将太多“相同”的对象放入缓存,所有缓存对象的实际对象都可能被回收,但生成它们的缓冲区可能仍在内存中。这看起来有些冗余,但在我的经验中,事实并非如此。
那么,你创建一个对象的 Cachecache 将对象序列化并调用 CacheManagerCacheManager 将尝试重用相同缓冲区的 ID 或请求保存它…但它会被保存在哪里?

这就是持久化器的作用。在框架中,唯一存在的且已经可用的持久化器是 FileCachePersister。它简单地接收参数并尝试加载文件,如果存在,则返回 null。它接收文件名并尝试更新其日期/时间以使其保持存活,或者保存文件。这样做是为了方便你创建持久化器来将数据存储到数据库,或使用远程服务器,它也可以有自己的缓存,避免并发进程访问相同文件。这很重要,因为 FileCachePersister 在多线程环境下工作得非常好,但只有一个进程必须使用该目录。
好的,在 FileCachePersister 中有一个线程用于删除旧文件,但这并不是什么复杂的事情。

ViewState

ViewState 解决方案与 Cache 解决方案非常相似,但它还包含额外的安全信息。负责加载和保存 ViewStates 的类是 PfzPageStatePersister。与 CacheManager 类类似,它有一个由 SessionIds 组成的字典,所以 ViewIds 对当前 Session 是独占的,然后值是 ViewIds 的字典,该字典的值是生成 ViewState 的页面类型(因此将 ViewId 复制到另一个页面将不起作用),以及 ViewState 的实际信息。
或者,更好的是,一个指向该值的缓存。为什么?因为如果你从一页转到另一页,生成相同的 viewstates,只会生成一个新的缓冲区“引用”,但该缓冲区(可能非常大)是相同的。它看起来有点复杂,因为它有一个 Pair,但这是因为 PageStatesPersisters 的通用工作方式,它们只生成两个对象,其唯一目的是被序列化。说实话,不太友好。

但,这里的想法是一样的:
查看 ViewState 是否在内存中。如果不在,则请求 Persister 加载它。
保存时,在内存中搜索一个相同的 ViewState,或者创建一个新的,调用 persister 来保存它。

Using the Code

CacheManager 类是这一切的起点。如果你只使用框架来将 viewstates 保存到文件中,那么它也在这里结束。在 Global.asax 文件中,添加以下内容(或类似内容):

   CacheManager.Persister = new FileCachePersister
	("c:\\temp\\PfzCachingWebApplication_ViewStates\\");
   CacheManager.Start();
        

其他有趣的事情

GCUtils - 我已经在另一篇文章中介绍过这个类,但现在它已被修改。通过注册到 Collected 事件,您可以了解最近发生的垃圾回收,如果您有任何额外的内存可以释放(例如调用 TrimExcess)。GCUtils.KeepAlive 也非常有用,可以告知 GarbageCollector 避免回收最近使用的对象,即使它们只有弱引用。

Cache 类用法 - 您可以在 Windows Forms 中使用 Cache 类,或者当您确实需要将一些大信息放入 Session,而不是放入 ViewState

例如,当您将对象放入 Session 时,您会创建一个 cache 对象并将 cache 对象放入 Session 中。

  Session["MyVeryLargeItem"] = new Cache<byte[]>(new byte[5000000]);

读取时,您这样做:

  byte[] data = ((Cache<byte[]>)Session["MyVeryLargeItem"]).Target;

在普通代码中,您仍然需要进行类型转换,但您通常不会创建缓存,也不会将其转换回缓存来读取目标。但是,这个额外的步骤会让您的 5MB 对象在 Session 中变成大约 32 字节,而 5MB 则保存在文件中。

后续相关主题文章

在未来的文章中,我计划用好的例子来解释 Cache 类、CacheDictionaryWeakDictionary 以及特别是 StatedPage 的用法。但这些是我个人框架的一部分,并非真正属于 ViewState 解决方案,因为它们独立于 ViewState,甚至可以在非 Web 应用程序中使用。

在这篇文章中,我只想展示 ViewState 解决方案,它确实有效(并且已经长期投入生产环境),并且不会改变编程方式,除了现在您可以分页记录并将整个数据集存储在 ViewState 中,因为它不会被发送到客户端。

历史

  • 2009 年 10 月 7 日:初始发布
© . All rights reserved.