为了创建可扩展高性能的基于WEB的应用ASPNET提供一个称为数据缓存(Data Caching)的特性数据缓存支持将频繁访问的数据对象可编程地存放在内存中这一特性可扩展以广泛地提高查询Oracle数据库中数据的ASPNET应用的性能本文讲述一个策略可用于采用Web Farm环境中的ASPNET Web应用缓存Oracle数据库数据这个技巧允许在内存中缓存频繁访问的Oracle数据库数据而不是频繁访问数据库来取数据这可以帮助避免到Oracle数据库服务器的不必要的远路进一步的文章提出了一个保持缓存数据以使其始终与Oracle数据同步的实现
ASPNET中的数据缓存
ASPNET中的数据缓存由Cache类和SystemWebCaching命名空间中的CacheDependency类支持Cache类提供向缓存插入和从中取出数据的方法CacheDependency类允许为缓存中数据项的指定其依赖项当我们用Insert和Add方法将项目加入缓存中可以指定一个项目的过期(expiration)策略我们可以用Insert方法的absoluteExpiration属性来定义缓存中一个项目的生命期这个属性允许你指定相应数据项过期的准确时间也可以使用slidingExpiration属性来指定项目过期的流逝时间(基于它被访问的时间)一旦一个项目过期它从缓存中被清除除非它再次被加入缓存中否则再试图访问将返回一个空值
设定缓存依赖
ASPNET使我们可以基于一个外部文件目录或另一个缓存项来定义一个缓存项的依赖即所谓文件依赖与键依赖若一个依赖项改变缓存项自动失效并被从缓存中清除当相应的数据源改变时我们可以用这种方法来从缓存中删除项目例如若我们的应用从一个XML文件中取数据并显示在一个表格(grid)中我们可以把文件中的数据存放到缓存中并设定缓存依赖于那个XML文件当XML文件被更新数据项就从缓存中被清除出去这一事件发生时应用重新读入XML文件最新的数据项副本被再一次插入缓存中进一步的回调事件处理器可被设定为一个监听者当缓存项被删除时得到通知这使得我们不需要反复轮询缓存来确定数据项是否已无效
Oracle数据库上的ASPNET缓存依赖
现在考虑这样一个情景数据存放于Oracle数据库中一个ASPNET应用通过ADONET来访问进一步我们假设数据库表中的数据一般是静态的并被这个Web应用频繁访问表上的DML操作很少而对数据有很多Select这种情况是数据缓存技术的理想应用但不幸的是ASPNET并不允许设定一个缓存项依赖于存放在数据库表中的数据进一步现实世界中基于Web的系统Web服务器和Oracle数据库服务器总是会运行在不同的机器上使得缓存无效操作更有挑战性另外多数基于Web的应用采用Web farms同一个应用的实例在不同的Web服务器上跑以负载均衡这种情况使得数据库缓存问题稍稍复杂一些
为了进一步研究上述问题的解决方案我们举一个Web应用的例子来说明如何实现例子中我们使用VBNET实现的ASPNET应用通过Oracle Data Provider for NET (ODP)来访问 Oracle i数据库
这个例子使用Oracle数据库中一个名为Employee的表我们为该表上insert update delete设定触发器这些触发器调用一个封装了一个Java存储过程的PL/SQL函数这个Java存储过程负责更新缓存依赖的文件
ASPNET Tier的VBNET实现
我们设计了含一个回调方法的监听类来处理缓存项无效时的通知这个回调方法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()
CacheInsert(employeeTable Source New
SystemWebCachingCacheDependency(d\download\tblemployeetxt)
CacheNoAbsoluteExpiration CacheNoSlidingExpiration
CacheItemPriorityNormal onRemove)
EndSub
方法getRecordFromdatabase()负责查询数据库表Employee并返回一个DataView对象引用它使用一个名为getEmployee的存储过程来抽象从Employee表中取数据的SQL这个方法有一个名为p_empid的参数表示Employee的主键
以下是引用片段
PublicFunction getRecordFromdatabase (ByVal p_empid As Int) As DataView
Dim con As OracleConnection = Nothing
Dim cmd As OracleCommand = Nothing
Dim ds As DataSet = Nothing
Try
con = getDatabaseConnection( UserId=scottPassword=tigerData Source=testingdb)
cmd = New OracleCommand(AdministratorgetEmployee con)
cmdCommandType = CommandTypeStoredProcedure
cmdParametersAdd(New OracleParameter(employeeId OracleDbTypeInt))Value = p_empid
Dim param AsNew OracleParameter(RC OracleDbTypeRefCursor)
cmdParametersAdd(param)Direction = ParameterDirectionOutput
Dim myCommand AsNew OracleDataAdapter(cmd)
ds = New DataSet
myCommandFill(ds)
Dim table As DataTable = dsTables()
Dim index As Int = tableRowsCount
Return dsTables()DefaultView
Catch ex As Exception
ThrowNew Exception(Exception in Database Tier Method getRecordFromdatabase () + exMessage ex)
Finally
Try
cmdDispose()
Catch ex As Exception
Finally
cmd = Nothing
EndTry
Try
conClose()
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 OracleDataAccessClientOracleConnection = Nothing
Try
con = New OracleDataAccessClientOracleConnection
conConnectionString = strconnection
conOpen()
Return con
Catch ex As Exception
ThrowNew Exception(Exception in Database Tier Method getOracleConnection()
+ exMessage ex)
EndTry
EndFunction
Oracle数据库Tier实现
定义Employee表上DML事件的触发器体如下这个触发器简单的调用一个PL/SQL包裹函数来更新名为tblemployeetxt的操作系统文件文件副本在两台机器(机器和机器)上更新两台机器运行同一个Web应用的不同实例来均衡负载这里administrator指Oracle数据库的方案(schema)对象所有者
beginadministratorplfile(machine\\download\\ tblemployeetxt)administratorplfile(machine\\download\\ tblemployeetxt)end
为更新缓存依赖文件我们需要写一个C函数或Java存储过程我们的例子中选择了Java存储过程因为Oracle数据库服务器有一个内置的JVM使得书写Java存储过程很方便必须有足够的内存分配给Oracle实例的系统全局区(SGA)中的Java池静态方法updateFile接受一个绝对路径作为参数并在合适的目录中创建缓存依赖文件若文件已经存在则先删除然后创建
import javaio*public class UpdFile {public static void updateFile(String filename) {try {File f = new File(filename)fdelete()fcreateNewFile()}catch (IOException e){// log exception}}
NPL/SQL包裹实现如下包裹函数以文件名为参数调用Java存储过程中updateFile方法
(p_filename IN VARCHAR)
AS LANGUAGE JAVA
NAME UpdFileupdateFile (javalangString)
Web Farm部署中的Oracle数据缓存
正如我们讨论的例子中所示Web服务器和机器构成了一个Web Farm来为我们的Web应用提供负载均衡每台机器运行同一个Web应用的一个实例在这个情况下每个实例可以拥有自己的存放在Cache对象中的缓存数据副本当Employee表改变相应的数据库触发器更新两台机器上的文件tblemployeetxt每个实例都指定一个到tblemployeetxt的缓存依赖Web Farm的两个实例都可以正确更新使得两个实例上的数据缓存可以和数据库表Employee保持同步
结论
数据缓存是优化Oracle数据库上ASPNET应用的有效技巧尽管ASPNET不允许设定缓存的数据库依赖Oracle触发器协同Java存储过程可以扩展ASPNET缓存的威力从而允许Oracle数据库缓存这个技巧也可以适用于Web Farm部署