优化SQL Server 2008 R2中的内存缓存层次结构

3
我将为C# Asp.Net MVC3网站在Sql Server中实现内容数据库。
表结构基本上是一种相邻列表,但我的文件和文件夹被分成了“FileSystem”,以便我可以隔离单个用户/账户的内容。
FileSystem:
  [ID]

Folder:
  [ID]
  [FileSystemID]
  [ParentFolderID] (null)
  [Name]

File:
  [ID]
  [FileSystemID]
  [ParentFolderID]
  [Name]
  [Content]

尽管在此进行非高效的规范化,但这是非常基本的东西。

我还有与[Created][Modified]相关的列。

这些文件将用于在Asp.Net MVC 3网站中执行页面动态品牌,其中每个请求都会寻找更具体的基于原始Asp.Net MVC内容URL的品牌版本的css/image文件 - 例如:"~/Content/Site.css"可能变成"~/[dynamic content root]/[accountid]/[theme]/Site.css"

这个基本机制已经完成了;我的主要关注点是缓存和版本控制。

显然,为了不必一直在数据库中探测相同的文件和文件夹,构建一个内存缓存来加速内容查找和交付是有意义的。然而,我需要一种有效的方式来确保Web农场中的所有Web服务器检测到帐户的虚拟文件系统(任何更改/删除/创建的文件或文件夹),以确保主题更改立即反映在所有服务器上,对于下一个请求。

由于分层查询可能很昂贵,因此我已经放弃了每次对文件系统中所有创建/最后修改日期进行健全性检查的方法。然而,我考虑过对整个文件系统进行级联版本号。

因此,文件系统中的任何更改都会导致文件系统本身的版本号增加,以及可能从更改项向上的所有父文件夹。因此,读者可以简单地附加到整个文件系统或特定部分,并每次运行廉价查询以检查当前版本与缓存版本是否相同。

这样做的缺点是会减缓更新速度,但我预计文件系统变化不频繁,而读取非常频繁。我唯一担心的是这种方法的并发性以及如何管理它。

这是一个好方法吗?还有什么更好的选择吗?

欢迎任何想法!

2个回答

3

由于您正在使用SQL Server,我建议使用类似SqlCacheDependency对象或SqlDependency对象作为SQL Server中的Query Notifications服务的一部分。

我已经在各种项目中成功使用了它,将通知的负担放在数据库上,而不是我自己编写的轮询机制。以下是我如何使用它来缓存角色信息的示例:

public CacheDependency GetRoleActionCacheDependency()
    {
        using (var connection = new SqlConnection(Database.Database.Connection.ConnectionString))
        {
            connection.Open();
            using (SqlCommand sc = new SqlCommand("select roleid, actionid from dbo.RoleAction", connection))
            {
                var dependency = new SqlCacheDependency(sc);
                sc.ExecuteNonQuery();
                connection.Close();
                return dependency;
            }
        }
    }

这个缓存依赖项会在roleaction表中的任何内容更改时使缓存失效。我可以通过在查询中设置参数来获得行级别的通知。

以下是我调用此代码的方式。您可以将实际的依赖对象存储在缓存中,但在我的特定情况下,对象实例本身存在于应用程序(静态)上,因此我不需要对其进行缓存,我只需要使其无效。我在这里通过将其设置为null来实现(getter管理重新填充它)。

CacheDependency rolePathAccessCacheDependency = GetRoleActionRepository().GetRoleActionCacheDependency();
    HttpContext.Current.Cache.Add("anything will do", new object(), rolePathAccessCacheDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal,
                    (key, value, reason) =>
                    {
                        _rolePathAccess = null; 
                    });

为了完成示例,您需要在global.asax应用程序启动中添加以下内容:

 SqlDependency.Start(ConfigurationManager.ConnectionStrings["DatabaseConnection"].ConnectionString);

应用程序终止:

SqlDependency.Stop(ConfigurationManager.ConnectionStrings["DatabaseConnection"].ConnectionString);

我忘了另外一件事情,那就是它依赖于SQL Server中的代理服务启用。以下是启用它的方法,但请注意,第一个语句会神奇地为您提供对数据库的独占访问权并回滚其他所有内容,因此如果您知道自己在做什么,只有在生产环境中才使用它。如果您已经拥有独占访问权限,则实际上只需要第二个语句。

ALTER DATABASE MYDatabase SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE MYDatabase SET ENABLE_BROKER
ALTER DATABASE MYDatabase SET MULTI_USER
GO        

啊,是的,我曾经想过这种方法可能会成为一个答案。我以前看过它,但对于数据库修改感到犹豫,不过这是一个全新的数据库,所以我可以尝试一下。谢谢,我会试试看的。 - Andras Zoltan
@Andras Zoltan,我已经成功地使用了这些功能。我从来没有遇到过启用代理服务的问题,当然,由于我在一家小公司工作,我也不必向任何数据库管理员磕头。 - Quesi
我需要再仔细考虑一下:我正在使用存储库模式;因此,将这个非常特定的功能整合到其中将会很有趣——存储库接口将不得不公开缓存依赖端点;但这是ASP.net特定的。啊,多层设计的诅咒。 - Andras Zoltan
@Andras Zoltan - 你可以将SqlDependency类封装在自己的类中,实现自己的接口并从存储库返回自己的接口。 SqlDependency类不是ASP.NET特定的,因为它位于System.Data.SqlClient命名空间中。然后,您可以实现自己的CacheDependency类,该类在内部使用您的新接口。您将获得相同的功能,并仍然能够将存储库与其他应用程序或其他存储库一起使用。我已经在Oracle上完成了这项工作。 - Quesi

0

这里是我的一些随机想法:

你是否真的有性能问题,并且能够将它们追溯到这个特定的问题?你需要快速修复还是深入研究更广泛的问题。你期望的吞吐量是多少,有具体数字吗?

在浏览器和服务器之间有几层,如果你允许的话,“网络”也可以在浏览器代理等地方进行缓存。然后就是IIS本身,它被设计用来处理缓存。MVC与那些(相对)静态的URL一起工作的方式,这两个都是“开箱即用”的。

下一层将是你的MVC对象模型,你能否将整个树保留在内存中?这样你就不必进行进程外调用了。这可以节省很多周期,更不用说去磁盘了。如果我理解正确,你仍然会共享很多文件,所以你可以共享这些实例(享元模式)。需要时进行延迟加载,并有一个释放机制。与你的时间相比,内存是便宜的。

敬礼 GJ


好的,目前还没有性能问题,但也还没有上线!可能过早优化,但经验和对流量水平的估计表明,尽可能少地访问数据库是最佳路径。我也将充分利用浏览器缓存;但我只是想确保,当我实现服务器缓存时,我的数据库结构适合使用它。你提出的问题很有道理,Quesi的回答很好,因为我不需要做太多工作来确保这一点。 - Andras Zoltan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接