技术开发 频道

ASP.net 缓存技术:延迟操作高级用法

 为了便于理解,我特意为大家准备了一个示例。整个示例由三部分组成:一个页面,一个JS文件,服务端代码。先来看页面代码:

<body>
<p> 为了简单,示例页面只处理一条记录,且将记录的RowGuid直接显示出来。<br />
实际场景中,这个RowGuid应该可以从一个表格的【当前选择行】中获取到。
</p>
<p> 当前选择行的 RowGuid = <span id="spanRowGuid"><%= Guid.NewGuid().ToString() %></span><br />
当前选择行的 Sequence
= <span id="spanSequence">0</span>
</p>
<p><input type="button" id="btnMoveUp" value="上移" />
<input type="button" id="btnMoveDown" value="下移" />
</p>
</body>

 

  页面的显示效果如下:

  处理页面中二个按钮的JS代码如下:

// 用户输入的调整记录的原因
var g_reason
= null;

$(
function(){
$(
"#btnMoveUp").click( function() { MoveRec(-1); } );
$(
"#btnMoveDown").click( function() { MoveRec(1); } );
});

function MoveRec(direction){
if( ~~($("#spanSequence").text()) + direction < 0 ){
alert(
"已经不能上移了。");
return;
}
if( g_reason == null ){
g_reason
= prompt("请输入调整记录顺序的原因:", "由于什么什么原因,我要调整...");
if( g_reason == null )
return;
}

$.ajax({
url:
"/AjaxDelaySendMail/MoveRec.fish",
data: { RowGuid: $(
"#spanRowGuid").text(),
Direction: direction,
Reason: g_reason
},
type:
"POST", dataType: "text",
success:
function(responseText){
$(
"#spanSequence").text(responseText);
}
});
}

  说明:在服务端,我使用了我在【用Asp.net写自己的服务框架】那篇博客中提供的服务框架, 服务端的全部代码是这个样子的:(注意代码中的注释)

/// <summary>
/// 移动记录的相关信息。
/// </summary>
public class MoveRecInfo
{
public string RowGuid;
public int Direction;
public string Reason;
}


[MyService]
public class AjaxDelaySendMail
{
[MyServiceMethod]
public int MoveRec(MoveRecInfo info)
{
// 这里就不验证从客户端传入的参数了。实际开发中这个是必须的。

// 先来调整记录的顺序,示例程序没有数据库,就用Cache来代替。
int sequence = 0;
int.TryParse(HttpRuntime.Cache[info.RowGuid] as string, out sequence);
// 简单地示例一下调整顺序。
sequence
+= info.Direction;
HttpRuntime.Cache[info.RowGuid]
= sequence.ToString();


string key = info.RowGuid +"_DelaySendMail";
// 这里我不直接发邮件,而是把这个信息放入Cache中,并设置2秒的滑过过期时间,并指定移除通知委托
// 将操作信息放在缓存,并且以覆盖形式放入,这样便可以实现保存最后状态。
// 注意:这里我用Insert方法。
HttpRuntime.Cache.Insert(key, info,
null, Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(
2.0), CacheItemPriority.NotRemovable, MoveRecInfoRemovedCallback);

return sequence;
}

private void MoveRecInfoRemovedCallback(string key, object value, CacheItemRemovedReason reason)
{
if( reason == CacheItemRemovedReason.Removed )
return;
// 忽略后续调用HttpRuntime.Cache.Insert()所触发的操作

// 能运行到这里,就表示是肯定是缓存过期了。
// 换句话说就是:用户2分钟再也没操作过了。

// 从参数value取回操作信息
MoveRecInfo info
= (MoveRecInfo)value;
// 这里可以对info做其它的处理。

// 最后发一次邮件。整个延迟发邮件的过程就处理完了。
MailSender.SendMail(info);
}
}

 

  为了能让JavaScript能直接调用C#中的方法,还需要在web.config中加入如下配置:

  

<httpHandlers>
<add path="*.fish" verb="*" validate="false" type="MySimpleServiceFramework.AjaxServiceHandler"/>
</httpHandlers>

  

  好了,示例代码就是这些。如果您有兴趣,可以在本文的结尾处下载这些示例代码,自己亲自感受一下利用Cache实现的【延迟处理】的功能。

  其实这种【延迟处理】的功能是很有用的,比如还有一种适用场景:有些数据记录可能需要频繁更新,如果每次更新都去写数据库,肯定会对数据库造成一定的压力, 但由于这些数据也不是特别重要,因此,我们可以利用这种【延迟处理】来将写数据库的时机进行合并处理, 最终我们可以实现:将多次的写入变成一次或者少量的写入操作,我称这样效果为:延迟合并写入

  这里我就对数据库的延迟合并写入提供一个思路:将需要写入的数据记录放入Cache,调用Insert方法并提供slidingExpiration和onRemoveCallback参数, 然后在CacheItemRemovedCallback回调委托中,模仿我前面的示例代码,将多次变成一次。不过,这样可能会有一个问题:如果数据是一直在修改,那么就一直不会写入数据库。 最后如果网站重启了,数据可能会丢失。如果担心这个问题,那么,可以在回调委托中,遇到CacheItemRemovedReason.Removed时,使用计数累加的方式,当到达一定数量后, 再写入数据库。比如:遇到10次CacheItemRemovedReason.Removed我就写一次数据库,这样就会将原来需要写10次的数据库操作变成一次了。 当然了,如果是其它移除原因,写数据库总是必要的。注意:对于金额这类敏感的数据,绝对不要使用这种方法。

  再补充二点:

  1. 当CacheItemRemovedCallback回调委托被调用时,缓存项已经不在Cache中了。

  2. 在CacheItemRemovedCallback回调委托中,我们还可以将缓存项重新放入缓存。

  有没有想过:这种设计可以构成一个循环?如果再结合参数slidingExpiration便可实现一个定时器的效果。

  关于缓存的失效时间,我要再提醒一点:通过absoluteExpiration, slidingExpiration参数所传入的时间,当缓存时间生效时,缓存对象并不会立即移除, ASP.NET Cache大约以20秒的频率去检查这些已过时的缓存项。

 

  巧用缓存项的移除通知 实现【自动加载配置文件】

  在本文的前部分的【文件依赖】小节中,有一个示例演示了:当配置文件更新后,页面可以显示最新的修改结果。 在那个示例中,为了简单,我直接将配置参数放在Cache中,每次使用时再从Cache中获取。 如果配置参数较多,这种做法或许也会影响性能,毕竟配置参数并不会经常修改,如果能直接访问一个静态变量就能获取到,应该会更快。 通常,我们可能会这样做: 『点击此处展开』

  但是,这种做法有一缺点就是:不能在配置文件更新后,自动加载最新的配置结果。

  为了解决这个问题,我们可以使用Cache提供的文件依赖以及移除通知功能。 前面的示例演示了移除后通知功能,这里我再演示一下移除前通知功能。

  说明:事实上,完成这个功能,可以仍然使用移除后通知,只是移除前通知我还没有演示,然而,这里使用移除前通知并没有显示它的独有的功能。

  下面的代码演示了在配置文件修改后,自动更新运行参数的实现方式:(注意代码中的注释) 『点击此处展开』

  改动很小,只是LoadRunOptions方法做了修改了而已,但是效果却很酷。

  还记得我在上篇博客【在.net中读写config文件的各种方法】的结尾处留下来的问题吗? 这个示例就是我的解决方案。

 

  文件监视技术的选择

  对于文件监视,我想有人或许会想到FileSystemWatcher。正好我就来说说关于【文件监视技术】的选择问题。

  说明,本文所有结论均为我个人的观点,仅供参考。

  这个组件,早在做WinForm开发时就用过了,对它也是印象比较深的。

  它有一个包装不好的地方是:事件会重复发出。比如:一次文件的保存操作,它却引发了二次事件。

  什么,你不信? 正好,我还准备了一个示例程序。

  说明:图片中显示了发生过二次事件,但我只是在修改了文件后,做了一次保存操作而已。 本文的结尾处有我的示例程序,您可以自己去试一下。这里为了方便,还是贴出相关代码:

private void Form1_Shown(object sender, EventArgs e)
{
this.fileSystemWatcher1.Path
= Environment.CurrentDirectory;
this.fileSystemWatcher1.Filter
= "RunOptions.xml";
this.fileSystemWatcher1.NotifyFilter
= System.IO.NotifyFilters.LastWrite;
this.fileSystemWatcher1.EnableRaisingEvents
= true;
}

private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
{
string message = string.Format("{0} {1}.", e.Name, e.ChangeType);
this.listBox1.Items.Add(message);
}

 

  对于这个类的使用,只想说一点:会引发的事件很多,因此一定要注意过滤。以下引用MSDN的一段说明:

  Windows 操作系统在 FileSystemWatcher 创建的缓冲区中通知组件文件发生更改。如果短时间内有很多更改,则缓冲区可能会溢出。这将导致组件失去对目录更改的跟踪,并且它将只提供一般性通知。使用 InternalBufferSize 属性来增加缓冲区大小的开销较大,因为它来自无法换出到磁盘的非页面内存,所以应确保缓冲区大小适中(尽量小,但也要有足够大小以便不会丢失任何文件更改事件)。若要避免缓冲区溢出,请使用 NotifyFilter 和 IncludeSubdirectories 属性,以便可以筛选掉不想要的更改通知。

  幸运的是,ASP.NET Cache并没有使用这个组件,我们不用担心文件依赖发引用的重复操作问题。 它直接依赖于webengine.dll所提供的API,因此,建议在ASP.NET应用程序中,优先使用Cache所提供的文件依赖功能。

 

  各种缓存方案的共存

  ASP.NET Cache是一种缓存技术,然而,我们在ASP.NET程序中还可以使用其它的缓存技术, 这些不同的缓存也各有各自的长处。由于ASP.NET Cache不能提供对外访问能力,因此,它不可能取代以memcached为代表的分布式缓存技术, 但它由于是不需要跨进程访问,效率也比分布式缓存的速度更快。如果将ASP.NET Cache设计成【一级缓存】, 分布式缓存设计成【二级缓存】,就像CPU的缓存那样,那么将能同时利用二者的所有的优点,实现更完美的功能以及速度。

  其实缓存是没有一个明确定义的技术,一个static变量也是一个缓存,一个static集合就是一个缓存容器了。 这种缓存与ASP.NET Cache相比起来,显然static变量的访问速度会更快,如果static集合不是设计得很差的话, 并发的冲突也可能会比ASP.NET Cache小,也正是因为这一点,static集合也有着广泛的使用。 然而,ASP.NET Cache的一些高级功能,如:过期时间,缓存依赖(包含文件依赖),移除通知,也是static集合不具备的。 因此,合理地同时使用它们,会让程序有着最好的性能,也同时拥有更强大的功能。

0
相关文章