15秒:为ASP.NET应用缓存Oracle数据

** 15 ** ** 秒:为 ** ** ASP.NET ** ** 应用缓存 ** ** Oracle ** ** 数据 ** ** **

原作: Narayan Veeramani

时间: 12/29 /2003

为了创建可扩展、高性能的基于 WEB 的应用, ASP.NET 提供一个称为数据缓存( Data Caching )的特性。数据缓存支持将频繁访问的数据对象可编程地存放在内存中。这一特性可扩展以广泛地提高查询 Oracle 数据库中数据的 ASP.NET 应用的性能。本文讲述一个策略,可用于采用 Web Farm 环境中的 ASP.NET Web 应用缓存 Oracle 数据库数据。这个技巧允许在内存中缓存频繁访问的 Oracle 数据库数据,而不是频繁访问数据库来取数据。这可以帮助避免到 Oracle 数据库服务器的不必要的远路。进一步的,文章提出了一个保持缓存数据以使其始终与 Oracle 数据同步的实现。

** ASP.NET ** ** 中的数据缓存 **

ASP.NET 中的数据缓存由 Cache 类和 System.Web.Caching 命名空间中的 CacheDependency 类支持。 Cache 类提供向缓存插入和从中取出数据的方法。 CacheDependency 类允许为缓存中数据项的指定其依赖项。当我们用 Insert 和 Add 方法将项目加入缓存中,可以指定一个项目的过期( expiration )策略。我们可以用 Insert 方法的 absoluteExpiration 属性来定义缓存中一个项目的生命期。这个属性允许你指定相应数据项过期的准确时间。也可以使用 slidingExpiration 属性来指定项目过期的流逝时间(基于它被访问的时间)。一旦一个项目过期,它从缓存中被清除。除非它再次被加入缓存中,否则再试图访问,将返回一个空值。

** 设定缓存依赖 **

ASP.NET 使我们可以基于一个外部文件、目录或另一个缓存项来定义一个缓存项的依赖,即所谓文件依赖与键依赖。若一个依赖项改变,缓存项自动失效并被从缓存中清除。当相应的数据源改变时,我们可以用这种方法来从缓存中删除项目。例如,若我们的应用从一个 XML 文件中取数据并显示在一个表格( grid )中,我们可以把文件中的数据存放到缓存中,并设定缓存依赖于那个 XML 文件。当 XML 文件被更新,数据项就从缓存中被清除出去。这一事件发生时,应用重新读入 XML 文件,最新的数据项副本被再一次插入缓存中。进一步的,回调事件处理器可被设定为一个监听者,当缓存项被删除时得到通知。这使得我们不需要反复轮询缓存来确定数据项是否已无效。

** Oracle ** ** 数据库上的 ASP.NET ** ** 缓存依赖 **

现在考虑这样一个情景:数据存放于 Oracle 数据库中,一个 ASP.NET 应用通过 ADO.NET 来访问。进一步,我们假设数据库表中的数据一般是静态的,并被这个 Web 应用频繁访问。表上的 DML 操作很少而对数据有很多 Select 。这种情况是数据缓存技术的理想应用。但不幸的是, ASP.NET 并不允许设定一个缓存项依赖于存放在数据库表中的数据。进一步,现实世界中,基于 Web 的系统, Web 服务器和 Oracle 数据库服务器总是会运行在不同的机器上,使得缓存无效操作更有挑战性。另外,多数基于 Web 的应用采用 Web farms ,同一个应用的实例在不同的 Web 服务器上跑以负载均衡。这种情况使得数据库缓存问题稍稍复杂一些。

为了进一步研究上述问题的解决方案,我们举一个 Web 应用的例子来说明如何实现。例子中,我们使用 VB.NET 实现的 ASP.NET 应用,通过 Oracle Data Provider for .NET (ODP) 来访问 Oracle 9i 数据库。

这个例子使用 Oracle 数据库中一个名为 Employee 的表。我们为该表上 insert, update, delete 设定触发器。这些触发器调用一个封装了一个 Java 存储过程的 PL/SQL 函数。这个 Java 存储过程负责更新缓存依赖的文件。

** ASP.NET Tier ** ** 的 VB.NET ** ** 实现 **

我们设计了含一个回调方法的监听类来处理缓存项无效时的通知。这个回调方法 RemovedCallback 用一个代理( delegate )函数来注册。回调方法 onRemove 的声明必须与 CacheItemRemovedCallback 代理声明又相同的签名。

Dim onRemove As CacheItemRemovedCallback = Nothing

onRemove = New CacheItemRemovedCallback(AddressOf RemovedCallback)

监听事件处理方法 RemovedCallback 负责处理数据库触发器的通知,其定义如下。若缓存项失效,可用数据库方法调用 getRecordFromdatabase() 从数据库取出数据。参数 ”key” 指从缓存中删除的项的索引位置。参数 ”value” 指从缓存中删除的数据对象。参数 "CacheItemRemovedReason" 指从缓存中删除数据项的原因。

PublicSub RemovedCallback(ByVal key AsString, ByVal value AsObject, ByVal reason As CacheItemRemovedReason)

Dim Source As DataView

Source = getRecordFromdatabase()

Cache.Insert("employeeTable ", Source, New

System.Web.Caching.CacheDependency("d:\download\tblemployee.txt"),

Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,

CacheItemPriority.Normal, onRemove)

EndSub

方法 getRecordFromdatabase() 负责查询数据库表 Employee 并返回一个 DataView 对象引用。它使用一个名为 getEmployee 的存储过程来抽象从 Employee 表中取数据的 SQL 。这个方法有一个名为 p_empid 的参数,表示 Employee 的主键。

PublicFunction getRecordFromdatabase (ByVal p_empid As Int32) As DataView

Dim con As OracleConnection = Nothing

Dim cmd As OracleCommand = Nothing

Dim ds As DataSet = Nothing

Try

con = getDatabaseConnection( "UserId=scott;Password=tiger;Data Source=testingdb;")

cmd = New OracleCommand("Administrator.getEmployee", con)

cmd.CommandType = CommandType.StoredProcedure

cmd.Parameters.Add(New OracleParameter("employeeId", OracleDbType.Int64)).Value = p_empid

Dim param AsNew OracleParameter("RC1", OracleDbType.RefCursor)

cmd.Parameters.Add(param).Direction = ParameterDirection.Output

Dim myCommand AsNew OracleDataAdapter(cmd)

ds = New DataSet

myCommand.Fill(ds)

Dim table As DataTable = ds.Tables(0)

Dim index As Int32 = table.Rows.Count

Return ds.Tables(0).DefaultView

Catch ex As Exception

ThrowNew Exception("Exception in Database Tier Method getRecordFromdatabase () " + ex.Message, ex)

Finally

Try

cmd.Dispose()

Catch ex As Exception

Finally

cmd = Nothing

EndTry

Try

con.Close()

Catch ex As Exception

Finally

con = Nothing

EndTry

EndTry

EndFunction

函数 getDatabaseConnection 接受一个连接字符串( connection stirng )为参数,返回一个 OracleConnection 对象引用。

PublicFunction getDatabaseConnection(ByVal strconnection as string) As OracleConnection

Dim con As Oracle.DataAccess.Client.OracleConnection = Nothing

Try

con = New Oracle.DataAccess.Client.OracleConnection

con.ConnectionString = strconnection

con.Open()

Return con

Catch ex As Exception

ThrowNew Exception("Exception in Database Tier Method getOracleConnection() " + ex.Message, ex)

EndTry

EndFunction

** Oracle ** ** 数据库 Tier ** ** 实现 **

定义 Employee 表上 DML 事件的触发器体如下。这个触发器简单的调用一个 PL/SQL 包裹函数来更新名为 tblemployee.txt 的操作系统文件。文件副本在两台机器(机器 1 和机器 2 )上更新。两台机器运行同一个 Web 应用的不同实例来均衡负载。这里 administrator 指 Oracle 数据库的方案( schema )对象所有者。

begin

administrator.plfile('machine1\\download\\ tblemployee.txt');

administrator.plfile('machine2\\download\\ tblemployee.txt');

end;

为更新缓存依赖文件,我们需要写一个 C 函数或 Java 存储过程。我们的例子中选择了 Java 存储过程,因为 Oracle 数据库服务器有一个内置的 JVM ,使得书写 Java 存储过程很方便。必须有足够的内存分配给 Oracle 实例的系统全局区( SGA )中的 Java 池。静态方法 updateFile 接受一个绝对路径作为参数,并在合适的目录中创建缓存依赖文件。若文件已经存在,则先删除然后创建。

import java.io.*;

public class UpdFile {public static void updateFile(String filename)

{

try {

File f = new File(filename);

f.delete();

f.createNewFile();

}

catch (IOException e)

{

// log exception

}

};

PL/SQL 包裹实现如下。包裹函数以文件名为参数,调用 Java 存储过程中 updateFile 方法。

(p_filename IN VARCHAR2)

AS LANGUAGE JAVA

NAME 'UpdFile.updateFile (java.lang.String)';

** Web Farm ** ** 部署中的 Oracle ** ** 数据缓存 **

正如我们讨论的例子中所示, Web 服务器 1 和机器 2 构成了一个 Web Farm 来为我们的 Web 应用提供负载均衡。每台机器运行同一个 Web 应用的一个实例。在这个情况下,每个实例可以拥有自己的存放在 Cache 对象中的缓存数据副本。当 Employee 表改变,相应的数据库触发器更新两台机器上的文件 tblemployee.txt 。每个实例都指定一个到 tblemployee.txt 的缓存依赖, Web Farm 的两个实例都可以正确更新,使得两个实例上的数据缓存可以和数据库表 Employee 保持同步。

** 结论 **

数据缓存是优化 Oracle 数据库上 ASP.NET 应用的有效技巧。尽管 ASP.NET 不允许设定缓存的数据库依赖, Oracle 触发器协同 Java 存储过程可以扩展 ASP.NET 缓存的威力从而允许 Oracle 数据库缓存。这个技巧也可以适用于 Web Farm 部署。

Published At
Categories with Web编程
Tagged with
comments powered by Disqus