修改网站内容( ConfigManager缓存行为不受控制且缓存时间不够长久缓存起来!)
优采云 发布时间: 2022-03-19 12:00修改网站内容(
ConfigManager缓存行为不受控制且缓存时间不够长久缓存起来!)
public class SetOutputCacheModule : IHttpModule
{
static SetOutputCacheModule()
{
// 加载配置文件
string xmlFilePath = Path.Combine(HttpRuntime.AppDomainAppPath, "OutputCache.config");
ConfigManager.LoadConfig(xmlFilePath);
}
public void Init(HttpApplication app)
{
app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
}
void app_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
Dictionary settings = ConfigManager.Settings;
if( settings == null )
throw new ConfigurationErrorsException("SetOutputCacheModule加载配置文件失败。");
// 实现方法:
// 查找配置参数,如果找到匹配的请求,就设置OutputCache
OutputCacheSetting setting = null;
if( settings.TryGetValue(app.Request.FilePath, out setting) ) {
setting.SetResponseCache(app.Context);
}
}
ConfigManager 类用于读取配置文件并启用文件依赖技术。当配置文件更新时,程序会自动重新加载:
internal static class ConfigManager
{
private static readonly string CacheKey = Guid.NewGuid().ToString();
private static Exception s_loadConfigException;
private static Dictionary s_settings;
public static Dictionary Settings
{
get{
Exception exceptin = s_loadConfigException;
if( exceptin != null )
throw exceptin;
return s_settings;
}
}
public static void LoadConfig(string xmlFilePath)
{
Dictionary dict = null;
try {
OutputCacheConfig config = XmlHelper.XmlDeserializeFromFile(xmlFilePath, Encoding.UTF8);
dict = config.Settings.ToDictionary(x => x.FilePath, StringComparer.OrdinalIgnoreCase);
}
catch( Exception ex ) {
s_loadConfigException = new System.Configuration.ConfigurationException(
"初始化SetOutputCacheModule时发生异常,请检查" + xmlFilePath + "文件是否配置正确。", ex);
}
if( dict != null ) {
// 注册缓存移除通知,以便在用户修改了配置文件后自动重新加载。
// 参考:细说 ASP.NET Cache 及其高级用法
// http://www.cnblogs.com/fish-li/archive/2011/12/27/2304063.html
CacheDependency dep = new CacheDependency(xmlFilePath);
HttpRuntime.Cache.Insert(CacheKey, xmlFilePath, dep,
Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, CacheRemovedCallback);
}
s_settings = dict;
}
private static void CacheRemovedCallback(string key, object value, CacheItemRemovedReason reason)
{
string xmlFilePath = (string)value;
// 由于事件发生时,文件可能还没有完全关闭,所以只好让程序稍等。
System.Threading.Thread.Sleep(3000);
// 重新加载配置文件
LoadConfig(xmlFilePath);
}
}
通过AutoSetOutputCacheModule,我们可以直接使用配置文件为页面设置OutputCache参数,无需修改任何页面。好用吗?
注意:MyMVC框架已经支持该功能,相关的可以从MyMVC框架的源码中获取。
建议:对于很少变化的页面,缓存页面是一种非常有效的优化方法。
回到顶部
启用内容过期
每个网站都会有一些资源文件(图片、JS、CSS),这些文件的输出与ASPX页面相比,很可能很长一段时间都不会改变。但是,IIS 在响应此类资源文件时不会生成 Cache-Control 响应标头。在这种情况下,浏览器可能会缓存它们,可能会再次发出请求(例如重新启动后),但缓存行为不受控制且缓存时间不够长。
你有没有想过让它们在浏览器中缓存很长时间?
为了告诉浏览器长期缓存这些文件,减少一些无意义的请求(提高页面渲染速度),我们可以在IIS中开启内容过期,设置后IIS可以生成Cache-Control响应header,明确告诉浏览器缓存文件多长时间。
在IIS6中,这个参数很容易找到:
但是,在IIS7中,这个参数不容易找到,需要进行如下操作才能找到: 选择网站(或网站子目录)节点,双击【HTTP Response Headers】 ]
点击右侧的【Set Common Header】链接,
这将显示:
注意:[Enable Content Expiration] 此设置可以基于整个网站,也可以针对子目录,或者特定文件。
注意:如果在 IIS7 中为子目录或文件设置了[Enable Content Expiration],之前的对话框看起来完全一样,但是在 IIS6 中,我们可以从对话框的标题栏中清楚地看到我们在做什么:
有时候真的感觉 IIS7 界面在倒退!
最后我想说:可以直接为整个网站开启内容过期,ASPX页面不会被缓存!
说到这里,可能有人会想:这个过期时间应该设置多久?十分钟、两小时、一天还是一个月?在我看来,这个时间越长越好。可能有人又要说:如果我要升级一个JS文件,时间设置了很久,用户怎么更新呢?如果你问我这个问题,我只能说你的代码不合理(毕竟不能解决升级问题),如果你想知道为什么,请继续阅读。
回到顶部
解决资源文件升级问题
对于一些小的网站,通常将资源文件和程序文件一起部署到一个网站中。这时候可以通过以下方式引用JS或者CSS文件:
在这种情况下,如果使用上面提到的【启用内容过期】方法,那么当有 JS 和 CSS 文件需要升级时,由于浏览器的缓存还没有过期,所以不会请求服务器。此时会使用缓存的版本,所以可能会出现各种奇怪的bug
对于上面提到的BUG,我认为根本原因是引用JS和CSS文件的方式有缺陷。该方法根本没有考虑版本升级的问题。正确的方法有两种: 1. 给文件名加上版本号,如jquery,每个版本一个文件(jquery-1.4.4.min.js)。 2. 在 URL 后添加版本号,使原来的 URL 失效。
第一种方法没有缓存问题,因为每次升级都会生成一个新文件,但是维护大量文件的成本可能比较大,所以我推荐第二种方法来解决。
在 MyMVC 示例代码中,我使用以下方法来引用这些资源文件:
页面运行时会产生如下输出:
这两个工具方法的实现代码如下(在MyMVC的示例代码中):
private static readonly string s_root = HttpRuntime.AppDomainAppPath.TrimEnd('\\');
public static string RefJsFileHtml(string path)
{
string filePath = s_root + path.Replace("/", "\\");
string version = File.GetLastWriteTimeUtc(filePath).Ticks.ToString();
return string.Format("\r\n", path, version);
}
public static string RefCssFileHtml(string path)
{
string filePath = s_root + path.Replace("/", "\\");
string version = File.GetLastWriteTimeUtc(filePath).Ticks.ToString();
return string.Format("\r\n", path, version);
}
以上获取文件版本号的方法是比较简单的解决方案。每个引用的地方在生成 HTML 代码时都会访问文件的最后修改时间,这会给磁盘带来一点读取开销。如果你担心这个实现可能会对性能产生影响,你也可以添加一个配置文件(请自行实现),比如如下结构:
如果你觉得这个配置文件需要手动维护,自动化程度不够,也可以用程序在运行时自动维护一个列表。总之,直接引用资源文件的方式是直接耦合,会给文件升级带来很多。来吧,我们可以用外部方法解耦这种直接耦合(给FileVersion加一个属性也可以把内部地址改成CDN地址)。
回到顶部
启用压缩
压缩响应结果也是一种常用的优化方法网站。由于现在所有浏览器都支持压缩功能,如果响应结果可以在服务器端进行压缩,对于网速较慢的用户来说,网络传输时间减少了很多,最终的体验就是网页的显示速度页面变得更快!
虽然IIS6提供了压缩设置接口,但配置是基于服务器级别的:
注意:这里的【应用文件】不包括aspx。如果需要压缩aspx的响应,需要手动修改x:\WINDOWS\system32\inetsrv\MetaBase.xml文件(参考放大字体部分):
注意:要修改 MetaBase.xml,需要停止 IIS Admin Service 服务。
在IIS7中,我们可以在服务器级别配置压缩参数:
然后在每个 网站 中打开或关闭压缩:
注意:在 IIS7 中不再使用 MetaBase.xml,所以我们找不到 IIS6 的那些设置。 IIS7 压缩的过滤条件不再针对扩展,而是使用 mimeType 规则(保存在 applicationHost.config 中)。根据IIS7的压缩规则,当我们开启动态压缩时,aspx的响应结果会被压缩。
两种压缩方式的区别: 1. 静态内容压缩:当服务器第一次响应静态文件时,会生成一个压缩的结果,并保存到磁盘中以供重复使用。 2. 动态内容压缩:【每次】响应客户端之前,响应结果会被压缩并在内存中完成,因此会对 CPU 造成一定的负担。
注意:是否[启用动态内容压缩]这个参数,需要评估服务器的CPU是否可以承受(观察任务管理器或查看性能计数器)。
回到顶部