1 业务需求
缓存来自数据库的数据,不用频繁到数据库中加载。
2 使用模型
添加一个类 MyCache,然后在里面添加静态属性字段:
public static DataTable FolderData { get { string key = "FolderData"; object o = HttpRuntime.Cache[key]; if (o == null) { //从DB中加载数据 o = LoadFromDB(); //添加到缓存中(分钟过期) HttpRuntime.Cache.Insert(key, o, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(30)); } return (DataTable)o; } }
逻辑是取数据时如果没有,则去数据库中加载,然后添加到缓存中以便下次使用。
前台调用时直接用MyCache. FolderData即可。
3 运行的问题
运行一段时间后,通过后台数据库的监控,发现有一台Web服务器还是有多次加载的SQL发送到数据库的情况,而且发生的还很频繁。为什么缓存没有生效呢?丢了?添加监控日志:
public static DataTable FolderData { get { string key = "FolderData"; object o = HttpRuntime.Cache[key]; if (o == null) { //从DB中加载数据 o = LoadFromDB(); saveLog("Add to Cache Key:" + key); //添加到缓存中(分钟过期)(指定回调函数) HttpRuntime.Cache.Insert(key, o, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(30), CacheItemPriority.Normal, ASPCacheRemoveCallBack); } return (DataTable)o; } }
A 添加进入Cache时,记录日志。
B 添加删除的回调函数,当缓存被删除时,记录日志。
private static void ASPCacheRemoveCallBack (string key, object value, CacheItemRemovedReason reason) { saveLog("Key:" + key + "被删除,原因:" + reason + ",时间:" + DateTime.Now.ToString()); }
然后分析这些日志,发现缓存有多次被删除,删除的原因是UnderUsed。
// 摘要:
// 之所以从缓存中移除该项,是因为系统要通过移除该项来释放内存。
Underused = 3,
但是连上服务器看,实际内存还有很多。(系统一共16G,使用的才11.5G,而且w3wp.exe进程才占用500MB。
为什么还有很多内存剩余,但是却说内存不够要把缓存的数据清理掉呢?因为是缓存,目的是少去数据库,缓存的东西如果多了,占用内存大了,OS不是还有虚拟内存吗?可以交换到硬盘呀,当然,如果这种情况发生,我们就把缓存的过期时间设小一点,少缓存点数据,维持系统平衡。但是不能还剩下4~5G的时候就不缓存了呀。
添加可用的日志:
private static void ASPCacheRemoveCallBack (string key, object value, CacheItemRemovedReason reason) { saveLog("Key:" + key + "被删除,原因:" + reason + ",时间:" + DateTime.Now.ToString() + "可用数:" + HttpRuntime.Cache.EffectivePrivateBytesLimit); }
查看日志,发现EffectivePrivateBytesLimit的值是0.
查阅资料:
http://msdn.microsoft.com/zh-SG/library/system.web.caching.cache.effectiveprivatebyteslimit(v=vs.80)
EffectivePrivateBytesLimit 属性返回应用程序进程可使用的千字节数。达到此限制后,缓存算法开始积极清理缓存。
可使用应用程序配置文件中的 caching的 cache 元素(ASP.NET 设置架构) 元素的 privateBytesLimit 属性 (Attribute) 设置 EffectivePrivateBytesLimit 属性 (Property)。未设置 privateBytesLimit 属性时,缓存算法将确定缓存的最大大小,EffectivePrivateBytesLimit 属性将包含该算法所选择的大小。
为0表示ASP.net 认为没有可分配的内存了吗?实际情况并不是这样,系统刚运行一个缓存也没有时,它也是输出0。
进一步查看Web.Config,我们并没有设定privateBytesLimit属性的值。
IIS上设定了最大内存限制吗?
也没有设定内存的限制,而且也没有设定要定时回收进程,这点可以从Application的变量没有重新初始化丢失得到验证。
同样的程序部署运行在另外好几套环境,EffectivePrivateBytesLimit的值也是0,但是它们没有频繁发生UnderUsed的情况。所以EffectivePrivateBytesLimit的值为0并不代表没有内存可用了,需要清理,反而更像没有对此属性做设定的意思。
那到底是什么原因引起的ASP.net 的清理内存呢?
继续查阅资料:http://msdn.microsoft.com/zh-cn/library/ms228248%28v=vs.100%29.aspx
发现可以有几个设定来控制缓存的设定:
根据实际运行情况, 我们猜想:ASP.net 面临内存压力时好像算错了,好吧,我们让它不要算了,只要清理过期的数据就好了。把disableMemoryCollection设定为true,告诉ASP.net面临内存压力时不要回收内存。
连续运行3天,ASP.net 正常清理过期的数据,但是没有再出现删除原因为UnderUsed的日志,内存占用也稳定,没有大的波动。
4 结语
本来还想继续深入寻找ASP.net Cache的缓存的清理内存机制到底是什么,到底为什么出问题,初步反射了下它的代码,没有准确找到,再说这个问题是 MS搞出来的,负责人应该是它,不应该我们帮它排错(哈哈,偷懒的借口,不过每个人的精力是有限的,要解决回答为什么的问题是无限的。。。)
本文最终的结论就是设定了一个配置值而已,这么小的事似乎没必要发Blog,但是发现大批提到UnderUsed和disableMemoryCollection的文章都只有1句话的解释,没有详细的分析排错的过程,故而献丑写一篇,如有不对,还请大家指出,谢谢!