2008年9月-10月
http://see.stanford.edu/default.aspx 在线课程
http://www.limboy.com/category/coder/js/ 中世纪风格网站
目前正在做的一个Web应用,是做了负载均衡的。在应用中,对某些实体数据做了Memory级别的缓存处理,以减少数据库访问次数,提高性能。缓存的实现是采用Asp.net 2.0的System.Web.Caching命名空间中的类。实体数据在某些情况下会有变更,所以需要对缓存进行处理,或清除或同步。在这种情况下产生了一个问题:实体数据发生变更之后,如何通知到后台的N多个Web应用中的缓存?下面方法是做的一些尝试。
1. 采用服务的方式
这是一种最直接的方式。当然服务的方式可以多种多样,比较简单的方式是提供一个ClearCache.aspx的页面,当实体数据发生变更之后调用N多台Web应该的这个页面。
2. 采用File Dependency的策略
这种策略让缓存依赖于一个指定的文件,通过改变文件的更新日期来清除缓存。这种方式的缺点是,如果缓存的数据比较多,相关的依赖文件比较松散,对管理这些依赖文件有一定的麻烦。对于负载均衡环境下,还需要同时更新多台Web服务器下的缓存文件,如果多个Web应用中的缓存依赖于同一个共享的文件,可能会省掉这个麻烦,但是对Web应用中运行帐号的权限所限,终归不是那么简洁。
HttpRuntime.Cache.Add(
"Key1",
new CacheDependency("C:\\test.txt"),
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null
);
3. 采用SqlCacheDependency的策略
这种策略让缓存依赖与数据库中指定的数据(查询结果)。可以用Poll的方式主动调用,设定一个周期,循环调用查询语句,如果查询结果发生变化,就会清除缓存。也可以配合Sql Server 2005,采用Push的方式被动的被通知什么时候会清楚缓存。这种Push的方式是基于Sql Server 2005中Broker Service的订阅服务,SqlCacheDependency需要配合SqlDependency来实现这种方式。
using System;
using System.Collections;
using System.Web;
using System.Web.Caching;
using System.Net;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace CacheManagement
{
public interface IWebCache
{
void Add(string key, object value);
object Get(string key);
void Remove(string key, bool useDependency);
}
public class WebCacheBase : IWebCache
{
protected bool CheckParameters(string key)
{
return !string.IsNullOrEmpty(key);
}
protected virtual CacheDependency GetCacheDependency(string key)
{
return null;
}
protected virtual void OnBeforeAdd(string key)
{
}
public virtual void Add(string key, object value)
{
if (!this.CheckParameters(key)) return;
this.OnBeforeAdd(key);
HttpRuntime.Cache.Insert(
key,
value,
this.GetCacheDependency(key),
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Default,
null);
}
public virtual object Get(string key)
{
if (!this.CheckParameters(key)) return null;
return HttpRuntime.Cache.Get(key);
}
public virtual void Remove(string key, bool useDependency)
{
if (!this.CheckParameters(key)) return;
HttpRuntime.Cache.Remove(key);
}
}
public class SqlDependencyWebCache : WebCacheBase
{
private string _connectionString;
private SqlConnection GetConnection()
{
return new SqlConnection(this._connectionString);
}
private SqlCommand GetSelectCommand(string key)
{
SqlConnection conn = this.GetConnection();
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "select Flag from dbo.CacheDependency where CacheKey=@CacheKey";
SqlParameter sqlParam = cmd.Parameters.Add("@CacheKey", SqlDbType.VarChar, 50);
sqlParam.Value = key;
return cmd;
}
private SqlCommand GetUpdateCommand(string key)
{
SqlConnection conn = this.GetConnection();
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = @"
if exists (select 1 from dbo.CacheDependency where CacheKey=@CacheKey)
update dbo.CacheDependency set Flag=case when isnull(flag,0)=0 then 1 else 1 end where CacheKey=@CacheKey
else
insert into dbo.CacheDependency (CacheKey,Flag)
values (@CacheKey, 0)
";
SqlParameter sqlParam = cmd.Parameters.Add("@CacheKey", SqlDbType.VarChar, 50);
sqlParam.Value = key;
return cmd;
}
private void UpdateCacheDependency(string key)
{
SqlCommand cmd = this.GetUpdateCommand(key);
using (cmd.Connection)
{
cmd.Connection.Open();
cmd.Prepare();
cmd.ExecuteNonQuery();
}
}
protected override void OnBeforeAdd(string key)
{
this.UpdateCacheDependency(key);
}
protected override CacheDependency GetCacheDependency(string key)
{
SqlCommand cmd = this.GetSelectCommand(key);
SqlCacheDependency sqlCacheDependency = new SqlCacheDependency(cmd);
using (cmd.Connection)
{
cmd.Connection.Open();
cmd.Prepare();
cmd.ExecuteNonQuery();
}
return sqlCacheDependency;
}
public SqlDependencyWebCache(string connectionString)
{
this._connectionString = connectionString;
}
public override void Remove(string key, bool useDependency)
{
if (!this.CheckParameters(key)) return;
if (useDependency)
{
this.UpdateCacheDependency(key);
}
else
{
base.Remove(key, useDependency);
}
}
}
public class WebCacheFactory
{
public static IWebCache GetWebCache()
{
string connectionString = ConfigurationManager.AppSettings["ConnectionString"];
return new SqlDependencyWebCache(connectionString);
}
}
}
<%@ Application Language="C#" %>
<script runat="server">
private string GetConnectionString()
{
return "Server=.;User ID=sa;Password=sa;database=TestDB;Connection Timeout=30";
}
void Application_Start(object sender, EventArgs e)
{
System.Data.SqlClient.SqlDependency.Start(this.GetConnectionString());
}
void Application_End(object sender, EventArgs e)
{
System.Data.SqlClient.SqlDependency.Stop(this.GetConnectionString());
}
</script>
注1:数据库中新建一个CacheDependency的表,主键为CacheKey varchar(50),另一个字段为Flag bit。缓存项依赖与CacheDependency中对应的一行。
注2:数据库中需要启动Broker Service。命令为:alter database [dbname] set enable_broker (需要断开其他的数据库连接)。
注3:在Applicantion Start和End的时候需要开启和停止SqlDependency。
结束语:
缓存的实现除了可以用Asp.net自带的Cache外,还可以用Entlib的Cache Application Block,没有仔细研究过。以上的三种方式是参考网上N多资源以及自己实践的结果。如果有建议或有其他方式,欢迎盖楼。