CSDN博客

img dhz123

动态刷新Cache in ASP.NET

发表于2004/10/29 11:17:00  1287人阅读

关于我们 | 广告业务 | 网站业务 | 联系我们  
 
 
 
 首页 文档中心 下载 论坛   
 
查看评注 评注   ASP & ASP.NET 网站性能设计之Cache动态使用
原创:zhoumj  2004年3月31日 
 

  ASP & ASP.NET 网站性能设计之Cache动态使用
原创:周梦杰  2004年2月21日 
下载文章的示例代码:demo.rar 
 


目录
摘要
应用范围和背景
ASP中Cache动态使用
walkthrough
ASP.NET中Cache动态使用
Cache.Add Method Reference
Cache随数据库动态变化的最佳实现原理
walkthrough
Code
总结
参考文献
作者简介
摘要
本文主要讲述如何利用Cache技术,来有效的提高网站性能。全文共涉及两个示例,一个是以ASP为基础介绍在ASP中使用Cache的方法;另一个则是以ASP.NET为基础,展示ASP.NET中使用Cache的新特性,特别值得一提的是Database dependencies技术,下面将逐步展开全文的讨论。

应用范围和背景
本文适合的读者对象:

熟悉Active Server Page   
对.NET架构有一定的了解 
对ASP.NET有基础 
熟悉Visual Studio.NET使用 
有较强的代码阅读学习能力 
熟悉Microsoft SQL Server各种工具使用 

Applies to: Microsoft® ASP.NET 
Microsoft® Active Server Page 
Microsoft SQL Server™2000 


信息化的时代已经到来,网站如雨后春笋般越建越多。相应地,开发网站的人专业技术人员也很多。无论是开发有单独服务器或集群的大型网站,还是租用虚拟空间的企业网站,性能(performance)都是重要的技术指标,尤其是首页刷新速度。据统计,对于网站的最终使用者来说,倘若他在5秒之内没有刷新出首页,或者在10秒之内没有刷新出其他页面,多数访问者会产生焦虑感,并对网站的友好度下降。更严重地,若网站由于访问人数太多而导致的服务器瘫痪(这里不考虑DOS or DDOS问题)或出错,则访问者对网站会产生不好的印象。尤其是公司的形象网站或以盈利为目的商业网站,他们对网站性能和稳定性的要求更高。

影响网站性能的因素很多(如虚拟空间或专线的传输速度等),对于开发人员来说,有很多技巧和最佳实现的方法来提高网站的访问速度,包括使用存储过程来提高数据查询效率等各种各种方法(本文不再一一叙述),当然最有效的还是通过合理使用Cache来极大地提高性能。

ASP中Cache动态使用
ASP从发布至今已经7年了,使用ASP技术已经相当成熟,自从微软推出了ASP.NET之后就逐渐停止了对ASP版本的更新。但是由于有很多人仍然习惯使用ASP来开发网站,所以,再次我将以一个简单的例子来说明在ASP中如何使用Cache。

简单的说使用Cache基本原理是,把经常需要且获得代价昂贵的数据在内存中持续保存一定时间,以供这些数据可以被直接地全局地访问。例如,有一些数据需要从数据库多个表中查询获得,且几乎每个页面都要调用这些数据。这种情况下的最佳实现就是将这部分数据Cache起来,在ASP中的简单实现就是将这些数据的最终表达形式(例如HTML流)封装在string中然后存入ASP内置对象Application中(本文主要讨论的是动态Cache,简单的ASP应用就省略)。这样做的好处是,在整个网站中可以全局调用这段HTML,而且Application是存在内存中,所以不用再去查询数据库,从而加快了响应时间并节省了服务器负荷。当然这是以消耗内存为代价的,是一个典型的以空间换时间的实例。

使用这种方法虽然有很多好处,但是再遇到频繁变化的数据源(数据库)的时候,这种方法就可能不再适用,因为ASP Application对象有一个缺点,就是不能自动随数据源的变化而变化,或者控制刷新间隔。所以就需要开发人员编程来实现动态Cache。当然在程序设计的时候可以在所有进行改变数据源(数据库)操作时,就更新一次Appliction。从而使数据源(数据库)始终保持一致。这样做在编程上要考虑的问题会比较多,容易遗漏细节。所以除了特定情况我不推荐使用这种方法。

我认为在ASP中最好的办法是用编程实现定时刷新Cache,也就是说给Application中储存的设一个过期时间。当然,在ASP中Application对象没有这样一个ExpireTime属性。这需要用程序实现。如本文的第一个示例。

walkthrough
准备工作:检查IIS是否安装且可用,检查SQL Server 2000是否安装且有默认的演示数据库NorthWind。

在IIS中建立虚拟目录,并注意权限设置,允许Internet 匿名用户IUSR_MACHINENAME访问。 
检查SQL Server 2000 NorthWind 数据库的权限设置,允许Internet 匿名用户IUSR_MACHINENAME对categories表有读权限。 
在建立虚拟目录的地方创建三个空白的演示文件:default.asp;getcache.asp;conn.asp。default.asp为前台展示页面,getcache.asp为后台控制Cache的文件,conn.asp是保存连接字符串的文件。 
将后文所示代码复制到这三个文件上即可。 
在IIS里浏览default.asp,察看时候顺利加载select控件。在刷新几次即可看出效果。在30秒之内不会在从数据库中读取而是从Application读取数据。从执行效果过来看,第一次的刷新速度明显慢于后面的刷新速度。如果有条件可以用SQL Server 2000自带的监视数据库的工具“事件探察器”监视就会发现只在第一次执行页面的时候执行SQL查询。 

使用这种方法的基本原理是,首先定义一个Cache变量(同样是储存在Application对象中)储存上一次和数据源同步的时间。在每一次新的刷新中判断当前时间与Cache中存的时间的间隔是否大于预先设定好的过期时间,若Cache未过期则直接从Cache读取HTML流,若Cache过期则重新读取数据源并重新在Cache写入同步时间,从而使Cache与数据源保持同步。

这种方法的使用很普及,但是并没有做到真正的即时同步。因为在ASP中数据源的变化很难或几乎不可能通知到ASP本身。这也是ASP方案和J2EE方案比较很略劣势的一方面。在Microsoft新一代建构.NET上却很好的实现了这种即时同步。下面的内容将重点介绍在ASP.NET中如何实现这种技术。

ASP.NET中Cache动态使用
我在使用ASP.NET的过程中发现在.NET Framework中有Cache对象用于专门管理的储存各种数据对象。这对以前在ASP中使用Application储存Cache的开发人员来说无疑是一大福音。Cache对象提供了很多优秀的特性,例如设置访问优先级策略,设置过期时间,在Cache过期时触发事件,自由管理Cache中的每一条记录等优点。但是本文我要重点讲述如何实现基于数据源改变事件触发Cache动态刷新的技巧。首先向讲述一下Cache.Add Method。

Cache.Add Method Reference
详情请见MSDN中Cache.Add Method。我再稍作一些解释 [C#]
public object Add(
    string key,
    object value,
    CacheDependency dependencies,
    DateTime absoluteExpiration,
    TimeSpan slidingExpiration,
    CacheItemPriority priority,
    CacheItemRemoved CallbackonRemoveCallback
);
 

key 用于检索Cache中对应的值 
value 将要在Cache中储存的对象 
dependencies 可以指定一个关联的文件或关联到一个其他Cache上,当这个文件或关联的Cache改变时,这个Cache就失效 
absoluteExpiration 指定绝对过期时间 
slidingExpiration 指定一个过期时间段 
priority 指定Cache的优先级 
CallbackonRemoveCallback  当这个Cache过期时,执行指定的委托函数 


Cache随数据库动态变化的最佳实现原理
至此,我们的最终目标已经很明确了。就是利用.NET Framework中提供的对象和方法实现Cache与数据源保持即时同步。突破点就是Cache.Add方法中的dependencies参数和CallbackonRemoveCallback参数,上文所述可以把一个Cache关联到一个文件或另一个Cache上。这样的话如果文件或另一个Cache改变时这个Cache就失效,然后就可以触发CallbackonRemoveCallback参数中指定的委托函数来重新刷新Cache。

所以对于数据源是文件系统的文件时,例如:XML文件等,就可以直接将dependencies指定成XML文件实际的物理路径。如下示例代码所示:

static Cache _cache = null; //声明全局Cache变量_cache

void Application_Start (){
    _cache = Context.Cache; //将ASP.NET环境上下文中的Cache对象给_cache
    RefreshCache (null, null, 0);
}

static void RefreshCache (string key, object item,CacheItemRemovedReason reason){
    string strHTML;
    strHTML = ReadSomeDataFromXML();//做一些处理,如从XML文件读出数据等
     _cache.Add(
        "HTML1",//key
        strHTML,//存入Cache中的value
        new CacheDependency ("C://CacheDependency//DataSource.xml"),//指定关联的文件
        Cache.NoAbsoluteExpiration,
        Cache.NoSlidingExpiration,
        CacheItemPriority.Default,
        new CacheItemRemovedCallback (RefreshCache)//当Cache失效时重新刷新
    );
}

 

但是多数数据源不是文件,而是数据数据库。若数据库改变时,或数据库某些表改变时,这种方法就不适用。既然文件可以和Cache做关联,这样我们就要想办法让数据库和某一个文件上关联,当数据库某些表改变时就能触发文件改变,从而使Cache重新刷新。按照这个思路,就不难想到SQl Server数据库系统的两种并不常用的技术: 触发器 triggers 
扩展存储过程 Extended Stored Procedures


 

原理如上图所示: 当数据库表中数据改变时(Update;Delete;Insert)将触动触发条件,触发器执行扩展存储过程。 
扩展存储过程调用的是预先编译好的COM+组件。 
这个COM+组件的功能是打开一个文件,然后再关闭这个文件,这样导致这个文件AT(access time)发生改变。 
由于Cache和这个文件时Dependency关系,所以文件发生改变时,将导致Cache无效。
正如上文所说,Cache无效时会触发委托函数,从而使Cache重新从数据库中读取数据。


walkthrough
由于这个示例不光包含代码,还涉及比较复杂的部署和系统调用,所以我将一步一步地详细说明。 在IIS中建立虚拟目录,假设实际目录是"C:/CacheDemo",虚拟目录是"CacheDemo".并注意权限设置,允许Internet 匿名用户IUSR_MACHINENAME访问。 
检查SQL Server 2000 NorthWind 数据库的权限设置,允许Internet 匿名用户IUSR_MACHINENAME对categories表有读权限。 
在建立虚拟目录的地方创建两个空白的演示文件:CacheDB.aspx;Global.asax; 
将后文所示代码复制到相应文件,并复查连接字符串是否正确。 
制作扩展存储过程COM+组件。打开Visual Studio.NET 
创建C++新工程,选Visual C++ projects -〉general 文件树中的Extended Stored Procedures DLL。


 

下一步指定扩展存储过程的名字是"xp_changefileAT"。 复制后文的xp_changefileAT代码到proc.cpp文件的xp_changefileAT的函数中。(覆盖函数中原来的代码)。其他文件保持不变。用Release模式编译程序。 
将ChangeFileAT.dll拷贝到你的SQL Server MSSQL/Binn 目录下。 
打开SQL Server 2000 Query Analyzer。选择Master数据库,执行sp_addextendedproc 'xp_changefileAT', 'ChangeFileAT.DLL'语句。sp_addextendedproc是用来加载扩展存储过程的内置存储过程。 
为NorthWind数据库的Categories添加触发器。在Query Analyzer 中选择NorthWind数据库,执行后文的addtrigger.sql。 
配置数据库,打开SQL Server 2000 Enterprise Manager,复查各种权限设置。 
在C:/CacheDemo中创建空白文件,文件名:Dependence(无扩展名),并配置权限,允许SQL Server有修改权限,默认情况不用额外配置。 
现在部署和配置完成。在IIS中浏览CacheDB.aspx是否看到预期的效果。如图 :


  若没有出现正确的网页,则复查上述每一步,并深刻理解原理图。若出现上图的效果则说明配置正确。 
打开SQL Server 2000 profiler。监视刷新页面时是否读取数据库。由于数据是从Cache中读取的,所以profiler不应该有任何响应。这说明Cache生效了。 
在SQL Server 2000 Enterprise Manager 中将Categories表中的"Beverages"改成"abcd"。 
再次刷新页面,看看是否自动刷新了Cache。如图:
在刷新几次页面,看看profiler是否有响应。由于数据是从更新过的Cache中读取的,所以profiler不应该有任何响应。这说明Cache更新生效。


 

总结
至此,本文最重要且最复杂的示例就讲完了。也许大家现在有一个疑问,就是为什么要依靠这么复杂的部署和设置才能达到这样效果,.NET没有提供现成的类来支持数据库和Cache同步吗?对于这个问题,我也困扰了好久。对于这个原因我个人也有一些推断,微软推出.NET战略是为了将J2EE击败,无论是从语言的先进性还是开发的方便性尤其是广大开发人员和客户所关心的执行效率问题上。.NET都力求胜人一筹。.NET Framework Cache 对象为什么选定文件系统来做Dependence对象,成为数据库和Cache的纽带,而不是全局的共享内存,其原因我也问过司徒彦南。他的解释是出于安全性和效率的考虑。

大家知道若依靠全局的共享内存来做不同进程之间的纽带,这种方法是不安全的,必须要处处小心,无论是读取还是写入都必须加锁,也就是说要访问一次共享内存需要Lock一次在UnLock一次,在读取的时候同样也需要Lock,这样频繁的锁操作是降低系统性能的,严重的还会产生死锁现象。
用文件系统做纽带的好处是,当文间被改的时候其实并不是实际去读写硬盘,而是利用操作系统的磁盘缓存特性,将操作转到内存中。而且通过更改32位整型的AT(access time)来表示是否修改过文件,这样的好处是在任何一个大于32位的操作系统中,对32位整型来说无论是读取还是修改都原子操作。从而达到无锁同步的目的。

综上所述,用这种方法充分的利用了操作系统,SQL Server 2000 和.NET的特性,在实现数据库和Cache同步做到了最佳实现。非常值得学习借鉴。

Code
ASP:default.asp

<%@Language=VBScript%>
<%Option  Explicit%>
<%Response.Buffer=True%>
<!--#include file = "conn.asp"-->
<!--#include file = "GetCache.asp"-->
<HTML>
    <HEAD>
        <TITLE>ASP Cache演示</TITLE>
        <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312">
    </HEAD>
    <BODY>
    <h4>每隔10秒刷新Cache:</h4>
    <%
    response.Flush
    GetHTMLStream
    response.Write
    HTMLStream
    %>
    </body>
</html> 


ASP:getcache.asp

<%
Const CACHE_DEFAULT_INTERVAL = 30 '每隔30秒刷新一次cache
Dim HTMLStream
Dim IsExpires
IsExpires = CacheExpires
Function CacheExpires
    Dim strLastUpdate
    Dim result strLastUpdate = Application("LastUpdate")
    If (strLastUpdate = "") Or (CACHE_DEFAULT_INTERVAL < DateDiff("s", strLastUpdate, Now)) Then
        result = true
        SetLastUpdateTime
    Else
        result = false
    End If
    CacheExpires = result
End Function

Sub SetLastUpdateTime
    Application.Lock
    Application("LastUpdate") = CStr(now())
    Application.UnLock
End Sub

Sub GetHTMLStream
    If IsExpires Then
        UpdateHTMLStream
    End If
    HTMLStream=Application("CACHE_HTMLStream")
End Sub

Sub UpdateHTMLStream
    dim d
    d = FetchHTMLStream
    Application.Lock
    Application("CACHE_HTMLStream") = d
    Application.UnLock
End Sub

Function FetchHTMLStream 
    Dim rs ,strSQL, strHTML
    Set rs = CreateObject("ADODB.Recordset")
    strSQL = "select categoryID , categoryname from categories"
    rs.Open strSQL, strConn,adOpenForwardOnly,adLockReadOnly
    strHTML = strHTML & "<select name=""slt_search"">"
    while (not rs.EOF)
        strHTML = strHTML & "<option>"
        strHTML = strHTML & rs.Fields("categoryname")
        strHTML = strHTML & "</option>" rs.MoveNext
    wend
    strHTML = strHTML & "</select>"
    rs.Close
    Set rs = Nothing
    FetchHTMLStream = strHTML
End Function
%> 


ASP:conn.asp

<!--METADATA NAME="Microsoft ActiveX Data Objects 2.5 Library" TYPE="TypeLib" UUID="{00000205-0000-0010-8000-00AA006D2EA4}"-->
<%
    dim strConn
    strConn = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Northwind"
%> 


C++ Extended Stored Procedures Dll:proc.cpp

RETCODE __declspec(dllexport) xp_changefileAT(SRV_PROC *srvproc) {
    //
    // Make sure an input parameter is present.
    //
    if (srv_rpcparams (srvproc) == 0)
        return -1;
    //
    // Extract the file name from the input parameter.
    // BYTE bType;
    char file[256];
    ULONG ulMaxLen = sizeof (file);
    ULONG ulActualLen;
    BOOL fNull;
    if (srv_paraminfo (srvproc, 1, &bType, &ulMaxLen, &ulActualLen, (BYTE*) file, &fNull) == FAIL)
        return -1;
    if (bType != SRVBIGCHAR && bType != SRVBIGVARCHAR)
        return -1; file[ulActualLen] = 0;
    //
    // Update the file's time stamp.
    //
    char path[288] = "C://CacheDemo//";
    lstrcat (path, file);
    HANDLE hFile = CreateFile (path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle (hFile);
    return 0;


SQL Server:Addtrigger.sql

CREATE TRIGGER DataChanged ON [dbo].[Categories]
FOR INSERT, UPDATE, DELETE
AS
EXEC master..xp_changefileAT 'Dependence'; 


ASP.NET:CacheDB.aspx

<%@ Import Namespace="System.Data" %>
<html>
    <body>
        <asp:Label ID="Author" RunAt="server" />
    </body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e) {
    string strHTML = (string) Cache["Quotes"];
    if (strHTML != null) {
        Author.Text = strHTML;
    }
    else {
        Author.Text = "Server busy";
    }
} </script> 


ASP.NET:Global.asax

<%@ Import NameSpace="System.Data" %>
<%@ Import NameSpace="System.Data.SqlClient" %>
<script language="C#" runat="server">
static Cache _cache = null;
void Application_Start ()
{
    _cache = Context.Cache;
    RefreshCache (null, null, 0);
}
static void RefreshCache (string key, object item,CacheItemRemovedReason reason)
{
    string mySelectQuery = "SELECT CategoryName FROM Categories";
    SqlConnection myConnection = new SqlConnection("workstation id=localhost;
                                                    packet size=4096;integrated
                                                    security=SSPI;data source=(local);
                                                    persist security info=False;
                                                    initial catalog=Northwind");
    SqlCommand myCommand = new SqlCommand(mySelectQuery,myConnection);
    myConnection.Open();
    SqlDataReader myReader = myCommand.ExecuteReader();
    StringBuilder sb = new StringBuilder();
    try
    {
        sb.Append("<select>");
        while (myReader.Read())
        {
            sb.Append("<option>");
            sb.Append(myReader.GetString(0));
            sb.Append("</option>");
        }
        sb.Append("</select>");
    }
    finally
    {
        myReader.Close();
        myConnection.Close();
    }
    _cache.Insert ( "Quotes",
                    sb.ToString(),
                    new CacheDependency ("C://CacheDemo//Dependence"),
                    Cache.NoAbsoluteExpiration,
                    Cache.NoSlidingExpiration,
                    CacheItemPriority.Default,
                    new CacheItemRemovedCallback (RefreshCache)
     );
}
</script> 

参考文献
[1]  MSDN Supporting Database Cache Dependencies in ASP.NET Jeff Prosise 
[2]  MSDN 25+ ASP Tips to Improve Performance and Style Len Cardinal 
[3] Worx Professional ASP.NET Performance K.Scott Allen 

作者简介

周梦杰,北京工业大学计算机学院2000级学生,2003年全国微软创新杯程序设计大赛三等奖。目前担任放飞项目工作室主管。您可以通过电子邮件  try@frontfree.net 与他联系 。

版权声明

本文版权归原作者和放飞技术网共同所有,转载本文必须注明作者和出处:放飞技术网。

 

 
 
 
 ©2001-2004 版权归放飞技术小组所有。放飞技术小组保留对此的一切权利。
 ©2001-2004 Frontfree Workgroup. All rights reserved. 全部文章 

阅读全文
0 0

相关文章推荐

img
取 消
img