避免10 个常见ASP.NET缺陷 使网站平稳运行
Sessions and Output Caching
会话和输出缓存
Speaking of output caching, there’s a potential issue with ASP.NET 1.1 and ASP.NET 2.0 that affects output-cached pages on servers running on Windows Server™ 2003 and IIS 6.0. I’ve personally seen this manifest itself on production ASP.NET servers twice, and both times it was resolved by turning off output caching. I later learned there’s a better solution that doesn’t require output caching to be disabled. Here’s how I first encountered the problem.
谈到输出缓存,ASP.NET 1.1 和 ASP.NET 2.0 都存在一个潜在的问题,该问题会影响在 Windows Server™ 2003 和 IIS 6.0 上运行的服务器中的输出缓存页。我曾经亲眼看到该问题在 ASP.NET 生产服务器中出现过两次,这两次都是通过关闭输出缓冲来解决的。后来我了解到有一个比禁用输出缓存更好的解决方案。以下是我第一次遇到该问题时的情况。
The saga began when a dot-com—let’s call it Contoso.com—that runs a public e-commerce app on a small ASP.NET Web farm contacted my team and complained that they were experiencing "cross-threading" errors. Every now and then, a customer using the Contoso.com Web site would suddenly lose the data they had entered and instead would see data corresponding to another user. A bit of sleuthing revealed that cross-threading wasn’t an accurate description; "cross-session" errors was more like it. It seems that Contoso.com was storing data in session state, and for some reason users were occasionally—and randomly—being connected to other users’ sessions.
当时的情况是这样的,某个网站(我们在此称为 Contoso.com,它在小型 ASP.NET Web 领域中运行公共电子商务应用程序)与我的团队联系,抱怨他们遇到了“跨线程”错误。使用 Contoso.com 网站的客户常常突然丢失已经输入的数据,但却看到另一用户的相关数据。稍做分析即发现,跨线程这个描述并不准确;“跨会话”错误更为贴切。看起来 Contoso.com 是在会话状态中存储数据的,由于某些原因,用户会偶尔随机地连接到其他用户的会话。
One of my team members wrote a diagnostic tool to log key elements of each HTTP request and response, including cookie headers. Then he installed it on Contoso.com’s Web servers and let it run for a few days. The results were remarkable. Roughly once in every 100,000 requests, ASP.NET was correctly assigning a session ID to a brand new session and returning the session ID in a Set-Cookie header. It would then return the same session ID (that is, the same Set-Cookie header) in the very next request, even if that request was already associated with a valid session and was correctly submitting the session ID in a cookie. In effect, ASP.NET was randomly switching users away from their own sessions and connecting them to other sessions.
我的一个团队成员编写了一个诊断工具,用来将每个 HTTP 请求和响应的关键要素(包括 Cookie 标头)记录到日志中。然后,他将该工具安装在 Contoso.com 的 Web 服务器上,并让其运行了几天。结果非常明显。大概每 100000 个请求中会发生一次这样的情况:ASP.NET 正确地为全新会话分配一个会话 ID 并返回 Set-Cookie 标头中的会话 ID。然后,它会在下一个紧相邻的请求中返回相同的会话 ID(即,相同的 Set-Cookie 标头),即使该请求已经与一个有效的会话相关联并且正确提交了 Cookie 中的会话 ID。实际上,ASP.NET 是随机将用户从他们自己的会话中切换出去并将他们连接到其他会话。
Astonished, we began to look for causes. We first examined Contoso.com’s source code and satisfied ourselves that the problem lay elsewhere. Next, just to be sure the problem wasn’t related to the fact that the application was hosted on a Web farm, we turned off all the servers but one. The problem persisted, which wasn’t surprising since our logs showed that the matching Set-Cookie headers never came from two different servers. It wasn’t credible that ASP.NET accidentally generated duplicate session IDs because it uses the .NET Framework RNGCryptoServiceProvider class to generate those IDs, and session IDs are of sufficient length to ensure that the same one will never be generated twice (not in the next trillion years, anyway). Besides, even if RNGCryptoServiceProvider was erroneously generating duplicate random numbers, that wouldn’t explain why ASP.NET mysteriously replaced valid session IDs with new (and non-unique) ones.
我们很惊讶,于是开始寻找原因。我们首先检查了 Contoso.com 的源代码,让我们感到欣慰的是,问题不在那。接着,为了确保问题与应用程序宿主在 Web 领域无关,我们只保留一个服务器在运行,而关闭了所有其他服务器。问题仍然存在,这并不意外,因为我们的日志显示匹配的 Set-Cookie 标头绝不会来自两个不同的服务器。ASP.NET 意外地生成了重复的会话 ID,这令人难以置信,因为它使用 .NET Framework RNGCryptoServiceProvider 类生成这些 ID,并且会话 ID 的长度足以确保相同的 ID 决不会生成两次(至少在下一个万亿年内不会生成两次)。除此之外,即使 RNGCryptoServiceProvider 错误地生成了重复的随机数字,也无法解释 ASP.NET 为何不可思议地将有效的会话 ID 替换为新的 ID(不唯一)。
On a hunch, we decided to look at output caching. When OutputCacheModule caches HTTP responses, it must be careful not to cache Set-Cookie headers; otherwise, a cached response containing a new session ID would connect all recipients of the cached response (as well as the user whose request generated the cached response) to the same session. We checked the source code; Contoso.com had output caching enabled in two pages. We turned it off. Lo and behold, the application ran for days without a single cross-session incident. It has run without error for more than two years since. And we saw the exact same scenario play out at a different company with a different application and a different set of Web servers. As at Contoso.com, eliminating output caching made the problem go away.
凭直觉,我们决定看一下输出缓存。当 OutputCacheModule 缓存 HTTP 响应时,它必须小心不要缓存了 Set-Cookie 标头;否则,包含新会话 ID 的缓存响应会将缓存响应的所有接收者(以及其请求生成了缓存响应的用户)连接到同一会话。我们检查了源代码;Contoso.com 在两个页面中启用了输出缓存。我们关闭了输出缓存。结果,应用程序运行数天而没有发生一个跨会话问题。此后,它运行了两年多都没有发生任何错误。在具有不同应用程序和一组不同 Web 服务器的另一家公司中,我们看到完全相同的问题也消失了。就像在 Contoso.com 一样,消除输出缓存就能解决问题。
Microsoft has since confirmed that this behavior stems from a problem in OutputCacheModule. (There may be an update available by the time you read this.) When ASP.NET is paired with IIS 6.0 and kernel-mode caching is enabled, OutputCacheModule sometimes fails to strip Set-Cookie headers from the cached responses it passes to Http.sys. Here’s the specific sequence of events that causes the bug to manifest itself:
Microsoft 后来确认此行为源于 OutputCacheModule 中的问题。(当您阅读本文时,可能已经发布了更新。)当 ASP.NET 与 IIS 6.0 一起使用并且启用内核模式缓存时,OutputCacheModule 有时无法从它传递给 Http.sys 的缓存响应中删除 Set-Cookie 标头。下面是导致出现错误的特定事件顺序:
- A user who hasn’t visited the site recently (and therefore doesn’t have a corresponding session) requests a page for which output caching is enabled, but whose output isn’t currently available in the cache.
- The request executes code that accesses the user’s newly created session, causing a session ID cookie to be returned in a Set-Cookie header in the response.
- OutputCacheModule provides the output to Http.sys, but fails to strip the Set-Cookie header from the response.
- Http.sys returns the cached response in subsequent requests, inadvertently connecting other users to the session.
| • |
最近没有访问网站(因此也没有对应的会话)的用户请求一个启用了输出缓存的页面,但是其输出当前在缓存中不可用。 |
| • |
该请求执行用于访问用户最新创建的会话的代码,从而导致会话 ID Cookie 在响应的 Set-Cookie 标头中返回。 |
| • |
OutputCacheModule 向 Http.sys 提供输出,但是无法从响应中删除 Set-Cookie 标头。 |
| • |
Http.sys 在后续的请求中返回缓存响应,误将其他用户连接到会话。 |
The moral of the story? Session state and kernel-mode output caching don’t mix. If you use session state in a page that has output caching enabled, and if the application runs on IIS 6.0, then you need to turn off kernel-mode output caching. You’ll still get the benefit of output caching, but because kernel-mode output caching is substantially faster than ordinary output caching, the caching won’t be as effective.
故事的寓意又是什么呢?会话状态和内核模式输出缓存不能混合使用。如果您在启用输出缓存的页中使用会话状态,并且应用程序在 IIS 6.0 上运行,则您需要关闭内核模式输出缓存。您仍将受益于输出缓存,但是因为内核模式输出缓存比普通输出缓存快得多,所以缓存不会同样有效。
You can turn off kernel-mode output caching for individual pages by including VaryByParam="*" attributes in the page’s OutputCache directives, although doing so can cause memory requirements to explode. The safer alternative is to turn off kernel-mode caching for the entire application by including the following element in web.config:
<httpRuntime enableKernelOutputCache="false" />
You can also disable kernel-mode output caching globally—that is, for entire servers—with a registry setting. Whenever I hear about inexplicable things happening with sessions, I ask the customer if they’re using output caching in any of their pages. If the answer is yes, and if the host OS is Windows Server 2003, then I advise them to disable kernel-mode output caching. The problem usually goes away. If it doesn’t, then the bug is in their code. Be warned!
您可以通过在页面的 OutputCache 指令中包含 VaryByParam="*" 属性来关闭单个页面的内核模式输出缓存,虽然这样做可能导致内存需求骤增。另一种更安全的方法是通过在 web.config 中包含下列元素来关闭整个应用程序的内核模式缓存:
<httpRuntime enableKernelOutputCache="false" />
您还可以使用注册表设置来全局性地禁用内核模式输出缓存,即禁用全部服务器的内核模式输出缓存。
每次我听到客户报告会话发生了费解的问题,我都会询问他们是否在任何页面中使用了输出缓存。如果确实使用了输出缓存,并且宿主操作系统是 Windows Server 2003,我会建议他们禁用内核模式输出缓存。问题通常就会迎刃而解。如果问题没有解决,则错误存在于代码中。警惕!
from:asp学习网/title:避免10 个常见ASP.NET缺陷 使网站平稳运行/ time:2007-5-20 17:41:09
本文主题,1,ASP,NET