对于一个真正的企业级的应用来说,Caching肯定是一个不得不考虑的因素,合理、有效地利用Caching对于增强应用的Performance(减少对基于Persistent storage的IO操作)、Scalability(将数据进行缓存,减轻了对Database等资源的压力)和Availability(将数据进行缓存,可以应对一定时间内的网络问题、Web Service不可访问问题、Database的崩溃问题等等)。Enterprise Library的Caching Application Block为我们提供了一个易用的、可扩展的实现Caching的框架。借助于Caching Application Block,Administrator和Developer很容易实现基于Caching的管理和编程。由于Caching的本质在于将相对稳定的数据常驻内存,以避免对Persistent storage的IO操作的IO操作,所以有两个棘手的问题:Load Balance问题;Persistent storage和内存中数据同步的问题。本篇文章提供了一个解决方案通过SqlDependency实现SQL Server中的数据和Cache同步的问题。
一、Cache Item的过期策略
在默认的情况下,通过CAB(以下对Caching Application Block的简称,注意不是Composite UI Application Block )的CacheManager加入的cache item是永不过期的;但是CacheManager允许你在添加cache item的时候通过一个ICacheItemExpiration对象应用不同的过期策略。CAB定了一个以下一些class实现了ICacheItemExpiration,以提供不同的过期方式:
对于过期的cache item,会及时地被清理。所以要实现我们开篇提出的要求:实现Sql Server中的数据和Cache中的数据实现同步,我们可以通过创建基于Sql Server数据变化的cache item的过期策略。换句话说,和FileDependency,当Persistent storage(Database)的数据变化本检测到之后,对于得cache自动过期。但是,对于文件的修改和删除,我们和容易通过文件的最后更新日期或者是否存在来确定。对于Database中Table数据的变化的探测就不是那么简单了。不过SQL Server提供了一个SqlDependency的组建帮助我们很容易地实现了这样的功能。
二、创建基于SqlDependency的ICacheItemExpiration
SqlDependency是建立在SQL Server 2005的Service Broker之上。SqlDependency向SQL Server订阅一个Query Notification。当SQL Server检测到基于该Query的数据发生变化,向SqlDependency发送一个Notification,并触发SqlDependency的Changed事件,我们就可以通过改事件判断对应的cache item是否应该过期。
我们现在就来创建这样的一个ICacheItemExpiration。我们先看看ICacheItemExpiration的的定义:
1: public interface ICacheItemExpiration<!--CRLF-->
2: {
<!--CRLF-->
3: // Methods<!--CRLF-->
4: bool HasExpired();<!--CRLF-->
5: void Initialize(CacheItem owningCacheItem);<!--CRLF-->
6: void Notify();<!--CRLF-->
7: }
<!--CRLF-->
而判断过期的依据就是根据HasExpired方法,我们自定义的CacheItemExpiration就是实现了该方法,根据SqlDependency判断cache item是否过期。下面是SqlDependencyExpiration的定义(注:SqlDependencyExpiration的实现通过Enterprise Library DAAB实现DA操作):
1: namespace Artech.SqlDependencyCaching<!--CRLF-->
2: {
<!--CRLF-->
3: public class SqlDependencyExpiration : ICacheItemExpiration<!--CRLF-->
4: {
<!--CRLF-->
5: private static readonly CommandType DefaultComamndType = CommandType.StoredProcedure;<!--CRLF-->
6:
<!--CRLF-->
7: public event EventHandler Expired;<!--CRLF-->
8:
<!--CRLF-->
9: public bool HasChanged<!--CRLF-->
10: { get; set; }
<!--CRLF-->
11:
<!--CRLF-->
12: public string ConnectionName<!--CRLF-->
13: { get; set; }
<!--CRLF-->
14:
<!--CRLF-->
15: public SqlDependencyExpiration(string commandText, IDictionary<string, object> parameters) :<!--CRLF-->
16: this(commandText, DefaultComamndType, string.Empty, parameters)<!--CRLF-->
17: { }
<!--CRLF-->
18:
<!--CRLF-->
19: public SqlDependencyExpiration(string commandText, string connectionStringName, IDictionary<string, object> parameters) :<!--CRLF-->
20: this(commandText, DefaultComamndType, connectionStringName, parameters)<!--CRLF-->
21: { }
<!--CRLF-->
22:
<!--CRLF-->
23: public SqlDependencyExpiration(string commandText, CommandType commandType, IDictionary<string, object> parameters) :<!--CRLF-->
24: this(commandText, commandType, string.Empty, parameters)<!--CRLF-->
25: { }
<!--CRLF-->
26:
<!--CRLF-->
27: public SqlDependencyExpiration(string commandText, CommandType commandType, string connectionStringName, IDictionary<string, object> parameters)<!--CRLF-->
28: {
<!--CRLF-->
29: if (string.IsNullOrEmpty(connectionStringName))<!--CRLF-->
30: {
<!--CRLF-->
31: this.ConnectionName = DatabaseSettings.GetDatabaseSettings(ConfigurationSourceFactory.Create()).DefaultDatabase;<!--CRLF-->
32: }
<!--CRLF-->
33: else<!--CRLF-->
34: {
<!--CRLF-->
35: this.ConnectionName = connectionStringName;<!--CRLF-->
36: }
<!--CRLF-->
37:
<!--CRLF-->
38: SqlDependency.Start(ConfigurationManager.ConnectionStrings[this.ConnectionName].ConnectionString);<!--CRLF-->
39: using (SqlConnection sqlConnection = DatabaseFactory.CreateDatabase(this.ConnectionName).CreateConnection() as SqlConnection)<!--CRLF-->
40: {
<!--CRLF-->
41: SqlCommand command = new SqlCommand(commandText, sqlConnection);<!--CRLF-->
42: command.CommandType = commandType;
<!--CRLF-->
43: if (parameters != null)<!--CRLF-->
44: {
<!--CRLF-->
45: this.AddParameters(command, parameters);<!--CRLF-->
46: }
<!--CRLF-->
47: SqlDependency dependency = new SqlDependency(command);<!--CRLF-->
48: dependency.OnChange += delegate<!--CRLF-->
49: {
<!--CRLF-->
50: this.HasChanged = true;<!--CRLF-->
51: if (this.Expired != null)<!--CRLF-->
52: {
<!--CRLF-->
53: this.Expired(this, new EventArgs());<!--CRLF-->
54: }
<!--CRLF-->
55: };
<!--CRLF-->
56: if (sqlConnection.State != ConnectionState.Open)<!--CRLF-->
57: {
<!--CRLF-->
58: sqlConnection.Open();
<!--CRLF-->
59: }
<!--CRLF-->
60: command.ExecuteNonQuery();
<!--CRLF-->
61: }
<!--CRLF-->
62: }
<!--CRLF-->
63:
<!--CRLF-->
64: private void AddParameters(SqlCommand command, IDictionary<string, object> parameters)<!--CRLF-->
65: {
<!--CRLF-->
66: command.Parameters.Clear();
<!--CRLF-->
67: foreach (var parameter in parameters)<!--CRLF-->
68: {
<!--CRLF-->
69: string parameterName = parameter.Key;<!--CRLF-->
70: if (!parameter.Key.StartsWith("@"))<!--CRLF-->
71: {
<!--CRLF-->
72: parameterName = "@" + parameterName;<!--CRLF-->
73: }
<!--CRLF-->
74:
<!--CRLF-->
75: command.Parameters.Add(new SqlParameter(parameterName, parameter.Value));<!--CRLF-->
76: }
<!--CRLF-->
77: }
<!--CRLF-->
78:
<!--CRLF-->
79: #region ICacheItemExpiration Members<!--CRLF-->
80:
<!--CRLF-->
81: public bool HasExpired()<!--CRLF-->
82: {
<!--CRLF-->
83: bool indicator = this.HasChanged;<!--CRLF-->
84: this.HasChanged = false;<!--CRLF-->
85: return indicator;<!--CRLF-->
86: }
<!--CRLF-->
87:
<!--CRLF-->
88: public void Initialize(CacheItem owningCacheItem)<!--CRLF-->
89: { }
<!--CRLF-->
90:
<!--CRLF-->
91: public void Notify()<!--CRLF-->
92: { }
<!--CRLF-->
93:
<!--CRLF-->
94: #endregion<!--CRLF-->
95: }
<!--CRLF-->
96: }
<!--CRLF-->
97:
<!--CRLF-->
我们来简单分析一下实现过程,先看看Property定义:
1: private static readonly CommandType DefaultComamndType = CommandType.StoredProcedure;<!--CRLF-->
2:
<!--CRLF-->
3: public event EventHandler Expired;<!--CRLF-->
4:
<!--CRLF-->
, courier, monospace; direction: lt
评论