构建可扩展和高性能的.NET Web应用程序(实用指南)






4.92/5 (55投票s)
为.NET架构师、经理和开发人员提供关于扩展、架构、管理和性能优化.NET Web应用程序的实用指南。
引言
本文旨在为.NET架构师、经理和开发人员提供一份实用指南,内容包括:
- 扩展.NET Web应用程序
- 架构大型.NET项目
- 管理.NET开发团队
- 优化.NET应用程序性能
背景
这是我的第一篇CodeProject文章。我是一个长期的读者,但通常情况下,我足够了解并能撰写的软件开发文章大多已经被写过了!我已经编写了各种不同的.NET Web应用程序一段时间,但由于我们初创公司Zignals(生产在线投资软件,如股票图表和市场警报)最近获得了资金,我突然发现自己必须开始认真考虑这些Web应用程序的扩展问题,包括管理更大的Visual Studio项目,管理一个开发团队而不是仅仅是我自己的项目,以及潜在用户数量及其带来的带宽和处理影响。我查阅了许多不同领域的网站,我认为我会将所学到的知识(尽可能地!)浓缩成一篇文章,供其他需要扩展项目、管理团队或从其Web应用程序中挤出额外速度的人参考。
扩展的一般架构考虑事项
多台IIS服务器(可热插拔)
拥有多个Web服务器来处理Web应用程序请求有多种原因。可用的Web服务器越多,处理用户请求的速度就越快,请求的吞吐量就越高(即在给定时间范围内可以响应更多的请求)。同时,还有自动故障转移带来的固有稳定性优势,即如果一个Web服务器发生故障,总有另一个可用来响应任何请求。
我将重点关注从.NET开发和项目管理角度的实际方面,而不会深入探讨此过程的管理员方面,因为这通常可以由您的托管公司处理。基本架构涉及一个网络负载均衡器(NLB),用于在Web服务器之间均匀路由请求,所有Web服务器都与同一个数据库服务器通信,例如:

这摘自此处。
如果您想了解此过程的系统管理方面的更多详细信息,请参阅此处的深入文章。
实际影响
那么,考虑到拥有多个Web服务器来处理Web用户的明显好处,实际影响是什么?
(这会涉及预算影响,具体取决于您使用的Web服务器。例如,如果是IIS,就像本例一样,那么每个Web服务器都需要自己的Windows服务器操作系统副本,例如Windows Server 2003,这会产生许可成本,可能需要考虑在内。在本文中,我只关注问题的技术方面,因此我不会在此处进行任何成本效益分析等)。
因此,从技术角度来看,多Web服务器架构的主要影响在于您的应用程序中如何处理Session
变量(以及随之而来的服务器端缓存和持久性)。如果您在ASP.NET中使用标准的Session
对象,那么您就会遇到问题。例如:
Session(“SomeKey”) = someObject
这行代码将对象someObject
添加到ASP.NET会话中,在正常的Web配置中,它依赖于当前正在处理用户请求的Web服务器。如果同一用户的所有后续请求都将由同一Web服务器处理(如单服务器模型),那么这不是问题。但第二个Web服务器不会原生访问在第一个Web服务器上创建的用户会话信息。
有多种方法可以解决这个问题。您可以使用web.config中的<sessionState>
属性来让IIS使用“StateServer
”或“SqlServer
”来维护状态(参见此处)。我们采用了一种双重方法,对于我们希望无限期且持续持久化的对象,我们自己处理其持久化,同时使用SQLServer
模式来处理一般的“临时”Session
信息。通过使用您自己的类来处理对象持久化,您最终将对对象的持久化有更长的控制权。例如,如果您使用状态服务器方法,每次重启服务器时都会丢失所有会话信息。本地实现的“SQL服务器”模式也是如此(尽管您可以通过一些调整来解决这个问题:参见此处)。我们选择SQLServer
模式而不是StateServer
模式进行临时持久化的原因是StateServer
固有的单点故障。如果SQLServer
宕机(这无论如何都是全站点问题),您至少可以将Sessions
表回滚到备份数据库服务器,而StateServer
模式则不行。
(顺便说一句,似乎没有办法“捕获”由StateServer
故障引起的错误,因为我们曾对此进行过实验,希望能在使用StateServer
时,因状态服务器故障导致的任何错误能强制临时Sessions
转移到我们自己的持久化类中。但是由于session
信息的实际保存直到回发完成后才发生,我们找不到一个好的位置来放置能捕获StateServer
故障的try catch
块。我们认为这种方法在性能和可靠性方面都是最好的,但由于我们无法捕获StateServer
存储函数中的错误,这证明是不可能的。如果其他人找到了解决方案,请告诉我!)
为了在多次访问中持久化数据和/或比标准会话更长的时间,我们使用自己的持久化类
string CreatePersistantStorage(ip)
void PeristObject(string sessionId, string key, object value)
object GetPersistentObject (string key)
因此,无论我们通常在哪里调用Session(“Key”) = Value
,并且您希望控制对象持久性,我们现在都会调用
PeristObject(persistentSessionId, “key”, value)
并且,相应地,所有
value = Session(“key”)
现在是
value = GetPersistentObject(persistentSessionId, “key”)
其实没什么大不了的。
由于两个IIS服务器都将指向同一个数据库服务器,因此它们将始终访问相同的持久信息。persistentSessionId
参数在会话创建时生成并作为cookie传回客户端
string persistentSessionId = Persistence.CreateNewPersistentSession(ip);
//Set up the UserID cookie
Response.Cookies["Persistent_Session"].Value = persistentSessionId;
Response.Cookies["Persistent_Session"].Expires = DateTime.Now.AddDays(365);
我个人会推荐“手动”管理持久化,原因有以下几点:
- 所有信息都可以根据您的需要持久化,无论web服务器或数据库服务器是否重启。
- 您可以完全控制信息的有效期。
- 您可以轻松地在多次访问中保留用户数据(例如“保持登录状态”)
- 您可以添加自定义功能(例如压缩大型持久化对象)
- 它使数据库备份和自动故障转移的处理变得更容易(例如,在数据库服务器故障转移期间,所有持久化会话都不会丢失)
- 而且它非常简单,所以没有真正的开发开销。
我们的PersistentSessions
表中只存储string
,因为我们有自己的持久化Caching
类,可以在其中存储任何我们想要的数据(稍后讨论)。但如果您想在Session
中存储除string
之外的对象,可以使用类似这样的方法
public static void AddItem(string itemKey, object item, bool doItemUpdate)
{
byte[] itemBytes = ZUtility.Serialize(item);
string query = string.Empty;
string sproc = string.Empty;
if (HasItem(itemKey)) // already in session, UPDATE item
{
if (doItemUpdate)
{
sproc = "dbo.p_UpdateObjectInSession";
}
}
else // not yet in session, INSERT item
{
sproc = "dbo.p_AddToSession";
}
if (!string.IsNullOrEmpty(sproc))
{
SqlQueryParam[] parameters =
{
new SqlQueryParam("@CacheKey", itemKey),
new SqlQueryParam("@CacheBytes", itemBytes, SqlDbType.Image)
};
SqlQuery.Execute(sproc, parameters,
SqlResultType.AffectedRows, CommandType.StoredProcedure);
}
}
注意:这要求对象被标记为可序列化
(尽管IIS的SQLSession
状态模式也是如此)。如果您使用这种方法处理长期对象持久性,向网站添加额外的Web服务器只需将编译后的项目(例如inetpub\wwwroot)复制到新服务器,设置IIS以匹配其他服务器,然后将其添加到集群中。
如果您不想将对象持久化超过标准Session
,您仍然存在跨多个web服务器处理Sessions
的问题,因此您需要查看.NET中的StateServer
和SQLServer
会话模式。一篇讨论处理会话的常规方法的简短文章可以在此处找到。
关于Web服务器群环境的最后一个实际点是数据库服务器。您可以轻松拥有许多Web服务器,但拥有多个SQL服务器则更为棘手。有一些技术文章介绍了如何实现这一点,所以我只说,一台快速机器上的1个SQL服务器如果配置正确,可以处理大量流量。主要问题,直到您达到每天数百万个请求,是“单点故障”问题。我们有一台SQL Server备用机,与主服务器并行运行,采用复制和故障转移方式,手动处理SQL Server中的持久化的好处是,SQL Server故障并不意味着持久化数据的丢失,因为备用服务器拥有所有这些数据的副本。
如果您愿意在镜像和故障转移方面稍作调整,那么这台备用服务器可以作为您的数据库缓存和持久化服务器,这样您就拥有了一台既能备份又能积极帮助提升站点性能的服务器。
大型Web应用程序的Visual Studio项目结构
拥有良好的项目结构是使大型Web应用程序易于管理的最佳方式,无论是对于个人还是特别是对于开发团队。并且将功能封装在.NET DLL项目中是任何Web应用程序的重要组成部分,我建议在开发所有新的Web应用程序时都考虑这一点。对于可能多方面、拥有众多独立用户界面或由大型团队(或多个团队)协同工作的大型Web项目来说,这一点尤为重要。为了最好地处理这个问题,我们在Zignals中使用的项目结构是:

Framework.dll
这存储了所有Web项目通用的底层类和函数。例如,我们的框架有:
- Session.cs:处理所有Web应用程序会话逻辑
- Logging.cs:处理所有错误和信息日志记录
- SqlWrapper.cs:处理所有与数据库的底层交互
- ZUtilities.cs:所有实用函数
- Security.cs:所有加密和安全函数
等等。基本上所有独立于项目的。如果您只构建一个大型Web项目,您可以将业务逻辑放在这个DLL中,以避免多个引用/命名空间的麻烦,但如果您希望在多个站点中重用您的通用函数,您应该为业务逻辑单独创建一个项目。
BusinessLogic.dll
包含为当前网站构建的各种Visual Studio项目通用的所有功能。将业务逻辑放在这里,可以使不同项目类型(例如Windows服务、Web服务、Web应用程序)的开发人员使用相同的底层业务逻辑。例如,在我们的案例中,我们允许用户在线模拟投资策略(Web应用程序)。我们还有一个复杂的算法,可以自动构建策略。由于处理要求,该算法作为Windows服务的一部分运行,允许在等待空闲CPU时排队处理。一旦策略自动构建完成,还需要在历史时间段内进行模拟以评估性能。由于我们将“SimulateStrategy
”函数放在BusinessLogic.dll中的Strategy
类中,因此Windows服务项目和Web应用程序项目都可以简单地引用BusinessLogic.dll项目输出,并始终使用最新版本。
CustomWebControls.dll
这个项目保存我们使用的所有ASCX文件。将其放在单独项目中的原因是,我们可以让开发人员独立于使用这些控件的开发人员来构建控件。这也意味着我们可以在多个Web项目中重用这些控件。它还允许我们从C#代码中动态添加这些控件。要详细了解如何创建用户控件库,请参见此处。
CustomControls.dll
我们有一个自定义控件项目,用于我们希望能够存储在数据库中的任何自定义对象(例如,序列化到我们的数据库缓存中)。单独项目和解决方案的原因是,DLL的重新编译会为对象创建不同的签名,并且在重新编译后,您将无法反序列化存储在数据库中的对象。由于您为每行新代码重新编译Web项目,这将导致序列化和将对象存储在数据库中变得不可能,因此需要CustomControls
库。顺便说一句,我们通常在此库中只包含非常基本的对象,因此很少需要重新编译。
WebApplication1
您通常会生成的任何Web应用程序。
Web应用程序2
如果需要。我们有多个Web应用程序,因为分工明确(例如,不同的开发人员或开发团队可以“自主”负责自己的项目),并且在一个项目中包含数百个ASPX文件或目录会很麻烦。

所有这些项目都可以作为同一个Visual Studio解决方案的一部分打开,并且您可以设置一个构建顺序,使框架首先编译,然后是业务逻辑,以此类推,直到Web应用程序。在上面的示例中,取自我们的主Visual Studio解决方案,您可以看到Framework和BusinessLogic项目以及两个Web项目(Dashboard
和ZignalsTools
)。对于我们的站点,CustomWebControls
项目名为WidgetControls
。
您还需要将CustomWebControls
项目的所有*.ascx文件复制到任何使用它们的Web应用程序的目录中。这可以通过将使用CustomWebControls
的Web项目的预构建事件设置为类似如下来完成
copy "$(SolutionDir)"CustomWebControls\*.ascx "$(ProjectDir)"UserControls\
您还需要确保链中每个上游项目都引用了链中下游项目的项目输出。例如,BusinessLogic.dll引用了Framework.dll的项目输出。这可以通过右键单击业务逻辑项目中的“添加引用”,然后在对话框中选择“项目”选项卡并选择相关项目来完成。

.NET Web服务器上的缓存
通过缓存通常来自数据库的常用数据,可以大大提高Web应用程序的性能。如果Web应用程序在显示原始数据之前必须对其执行一些复杂操作(财务计算、绘图等),或者数据来自第三方数据源,例如来自其他站点的RSS源或XML Web服务调用,那么这种性能提升会更加显著,因为获取数据相关的网络延迟可能是一个显著的延迟。
IIS和.NET中都有多种不同的数据缓存方式,可用于不同目的。在.NET中,有HttpRuntime.Cache
和HttpContext.Current.Cache
缓存类,可用于缓存对象。两者之间存在细微差别,但使用HttpRuntime.Cache
的一个很好的理由可以在此处找到。
那么,现在您有了内置的缓存类,调用...有什么问题呢?
HttpRuntime.Cache.Insert(“SomeKey”, someObject)
...用于所有可缓存的Web对象?
与上面的Sessions
问题类似,问题在于当您将应用程序从单个Web服务器环境迁移到双服务器、多服务器或Web服务器群环境时会发生什么。对一台服务器发出的数据请求的输出将缓存到该服务器上,但无法保证下一次对相同数据发出的请求会在Web服务器群中的同一Web服务器上进行,这意味着需要再次访问数据库并在新服务器上重新缓存数据,依此类推。

我们的解决方案是采用两级缓存:使用HttpRuntime.Cache
类实现的ASP.NET内存缓存和DBCache
,后者将对象序列化并存储在我们的Cache
数据库中。
通过采用双层方法,我们可以在第一次使用对象时访问数据库中的原始数据,然后将结果对象添加到DBCache
和MemoryCache
中。如果用户下一次对同一对象的请求发生在与之前相同的Web服务器上,他们将直接从IIS的inProc cache
(HttpRuntime
)获取对象。如果相同的请求发生在服务器群中不同的Web服务器上,用户的请求将通过从DBcache
中反序列化对象预生成(比从主数据库中的原始数据重新计算对象更快)。此Web服务器的应用程序缓存现在已填充相同的对象,因此下次在此Web服务器请求时,响应将直接来自内存缓存。
我们通常将这种双重缓存用于许多用户共享的生成对象。这意味着第一个用户请求将被缓存,之后任何用户的进一步请求都将从缓存中提供。
在我们这里有一个例子,当用户请求微软股票(MSFT)过去5年的14天移动平均线时。第一个请求从我们的Prices
表中获取原始Price
数据,并计算一个双精度数组,表示过去5年每天的MA值。很可能另一个用户会想要计算相同的值(或其中一部分,但我们如何从缓存中处理这是另一个故事!),因此我们将双精度数组序列化并存储在缓存中。随后的对相同计算的请求将不再需要访问庞大的Price
数据表或进行任何计算,唯一的问题是请求是由Web服务器上的IIS缓存还是由我们的数据库缓存来满足。

我们将所有数据库Cache
数据存储在我们的CacheServer
上,这是一个独立的物理服务器,运行着一个SQL Server副本(即独立于我们的主SQL Server)。顺便说一句,您不需要SQL Server的完整企业版作为Cache服务器,因为SQL Express版具有足够的强大功能和容量来满足缓存需求,而且它是免费的。
这种方法还具有额外的优势,即允许我们根据需要持久化缓存。例如,当Web服务器重新启动时,缓存不会被销毁,而且我们可以非常容易地添加额外的Web服务器,并且知道它们可以即时访问由在其他Web服务器上运行的Web应用程序生成的长期缓存对象。
关于我们的方法,有一点需要注意,它实际上是一个只读缓存,因为我们想要缓存的所有对象最终都是从我们的数据库服务器中的原始数据创建的,而不是由用户输入创建或修改的。这是一个重要点,因为如果您想要一个读写缓存,其中用户输入可能会覆盖缓存条目,那么您需要使用不同的方法。我推荐的一种方法,也是我们目前已经实现并正在试验的方法,是基于此处讨论的方法。这种方法也可以代替我们的只读缓存使用,但我们还没有足够的统计数据来说明速度提升与内存使用率之比是否高效(例如,如果任何Web服务器上的每个用户都创建数据,这些数据通过Web服务器群网络传输并存储在每个其他Web服务器上,那么了解这些数据是否经常被重用以使此操作有效是很重要的)
LRU策略
最后,我们对数据库缓存有LRU策略(IIS缓存原生实现了LRU策略,尽管文档中并未明确说明)。我们的缓存服务器上运行着一个缓存监控服务,它会自动删除超出“过期”日期的项目。添加新项目时,如果缓存中没有足够的“空间”,则会删除最近最少使用的项目。SQL Server缓存上的LRU策略通过将缓存表中的键按最近使用程度排序来处理。我们在缓存表中有一个列,它始终按从最近最少使用到最近最常使用的顺序排列。例如,访问缓存表中的一行时

因此,删除最近最少使用的项目意味着删除位置1的行,并将剩余行中的LRU列减1。(实际上,当我们执行LRU操作时,我们会删除大量行,以防止在插入新项目时不断更新行)。
Web应用程序的性能
可以通过前端和后端对Web应用程序进行许多操作,使其运行更快、使用更少带宽、利用更少服务器处理能力。在某些情况下,结果对最终用户来说可能非常显著,在其他情况下,结果对服务器/带宽将支持的并发用户数量来说可能非常显著。在所有情况下,这都是一个非常有益且富有成效的练习。我们花了很多时间在网上、各种论坛和用户群中搜索,试图整理出一份标准化的性能增强列表,这些增强措施实施开销低、可重复、易于开发人员在没有麻烦的情况下普遍实践,并且能产生可衡量的改进。我将在此处总结精简后的列表,但对于任何热衷于从服务器中榨取每一个处理周期的读者,我将在本文末尾列出“十大”链接,指向我们发现最有启发性的一些性能文章,所有这些文章都包含非常有用的信息。尽管它并非专门针对.NET应用程序,但如果您只选择一个来探索,我真的推荐Yahoo性能最佳实践列表。
JavaScript 单文件合并/压缩
将网页上的多个JavaScript文件替换为一个大型(压缩的)文件。将用于特定网页的每个JavaScript文件复制到一个名为AllScript.js的主JavaScript文件中。然后将引用这些文件的<script />
标签替换为对AllScript.js文件的一个单一脚本引用。此脚本引用应尽可能地放置在网页底部,以便视觉内容首先加载,而不会因JS文件减慢CSS文件和图像/媒体等内容的加载速度。页面中的所有JavaScript都应转移到外部JS文件中。
通过使用一个名为JSMIN的实用程序,可以将主JavaScript文件进一步缩小,它会从输入文件中删除空白并输出缩小后的文件。这个缩小版可以通过在ASPX文件底部引用AllScript_min.js来同样引用。这个文件也可以在asp:ScriptManager
/ToolScriptManager
控件中引用,方法是将ScriptReference
标签的Path
属性设置为文件的路径。如果LoadScriptsBeforeUI
属性设置为false
,那么任何引用的JS文件在渲染时都会放置在网页底部。使用Firefox的Firebug工具,我们可以检查页面运行时请求并下载到浏览器的所有JS文件。当使用AjaxControlToolkit
时,它使用的客户端JS文件名为ScriptResource.axd,它们被动态引用并下载到浏览器。这会导致大量单独的请求(这是我们希望避免的),因此存在一个选项,可以将这些文件合并到一个HTTP请求中。这可以通过将ToolScriptManager
控件上的CombineScripts
属性设置为true
来完成。ToolScriptmanager
继承自ScriptManager
控件,因此在ASPX页面中替代ScriptManager
控件是没问题的。
CSS 单文件合并/压缩
CSS文件应在HTML/ASPX页面的head
部分中引用,因为我们希望视觉内容在脚本文件之前加载。与上述JS单文件合并/压缩过程类似,我们可以将特定页面所需的所有引用CSS文件合并到一个名为AllCSS_min.css的单个主CSS文件中,并在header内部的<link />标签中引用它。CSS文件可以简单地复制/粘贴到主CSS文件中,并且一个名为CSSMIN的工具会将这些文件压缩成一个单独的CSS文件。
现在您可能会想,上面两个步骤都不用费心了,因为所有的JS和CSS文件在第一次访问后都会缓存在浏览器中,因为它们是静态内容。我对此也感到惊讶,但有很好的统计数据表明,每天访问给定网站的所有访客中,有40-60%是带着空的浏览器缓存来的。而且,对于首次用户来说,尽可能快的体验几乎比对于日常用户来说更重要。此外,如果每天40-60%的流量来自空缓存,并且您预计每天有大量用户,那么服务器负载将重得多,因为页面请求量可能会增加10倍。
IIS 6.0 压缩
启用压缩是必须的。在IIS 6.0(例如Windows 2003服务器)上,文件压缩默认是针对静态压缩开启的。要启用动态压缩,可以通过运行脚本或通过IIS 6.0激活。这篇文章详细解释了具体步骤。
CSS Sprites/多图合并
对于您网站中特定页面引用的每个图像,都会发出一个单独的HTTP请求将其下载到浏览器。如果您有大量图像,这会占用大量带宽,效率不高。相反,图像可以合并成一个更大的图像,并作为一次HTTP请求下载。在网页中希望显示图像的部分(如<img />
元素)都可以通过将src
属性设置为例如sprites1.png来引用相同的master图像,并提供background-position属性的偏移量,从master图像中“挑选”出所需的图像。一个名为CSS Sprites Generator的第三方工具使此过程变得更容易。只需上传特定网页上使用的所有图像并点击生成,该网站就会自动将所有图像合并成一个图像,并提供如下所示的偏移量
.info {background-image:url(sprites1.png);
background-position:-66px -66px;
}
.lightning {
background-image:url(sprites1.png);
background-position:-66px -246px;
}
.magnify {
background-image:url(sprites1.png);
background-position:-66px -510px;
}
注意:使用CSS属性 repeat 的图像元素不能在此过程中使用。动画的*.gif也无法工作。并且要向主图像添加新图像,您需要再次以上次相同的顺序上传主图像中的所有先前图像,以保持相同的偏移值。
记录主图像中每个图像的偏移量以供参考非常重要。
Web.config/Machine.config 最佳设置
对于生产网站,务必记住在Web.config中设置<Compilation debug =”false” />
。这可确保不会为网站的发布版本生成不必要的调试代码。如果您不使用某些ASP.NET模块,例如Windows身份验证或Passport身份验证等,则可以从ASP.NET处理管道中删除它们,否则它们将被不必要地加载。下面是一些可以从管道中删除的模块示例
<httpModules>
<remove name="WindowsAuthentication"/>
<remove name="PassportAuthentication"/>
<remove name="AnonymousIdentification"/>
<remove name="UrlAuthorization"/>
<remove name="FileAuthorization"/>
<remove name="OutputCache"/>
</httpModules>
ASP.NET进程模型配置定义了一些进程级别的属性,例如ASP.NET使用的线程数、在超时之前阻塞线程的时间、等待IO工作完成的请求数等等。对于拥有大量RAM的快速服务器,可以调整进程模型配置,使ASP.NET进程消耗更多系统资源,并从每台服务器提供更好的可扩展性。以下设置有助于提高性能(摘自一篇优秀文章的精简版,文章链接此处)
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="00:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
responseDeadlockInterval="00:03:00"
responseRestartDeadlockInterval="00:03:00"
maxWorkerThreads="100"
maxIoThreads="100"
minWorkerThreads="40"
minIoThreads="30"
asyncOption="20"
maxAppDomains="2000"
/>
缓存第三方数据和生成的图像
如果您正在从第三方站点获取数据(例如RSS源、混搭数据、Web服务等),那么将这些数据缓存一小段时间(取决于数据需要多么“实时”)可能是一个好主意。当有许多远程请求此类数据时,这会显着缩短页面加载时间。例如,在我们的案例中,我们允许用户指定他们感兴趣的RSS源。由于许多用户可以指定相同的热门源,我们可以将从远程站点返回的RSS数据作为XML缓存,并将其存储在数据库中一小段时间(例如10分钟)。通过这样做,只有第一个请求RSS源的用户才会遇到延迟,因为我们的服务器必须向RSS数据所在的远程服务器发送请求。在缓存期间,所有后续用户将直接从我们的缓存接收他们的数据,从而消除了与联系远程服务器相关的延迟和带宽要求。
我们还使用了一个第三方图表控件,它在创建图表时会在服务器上生成图像(*.png/*.jpeg)。当用户指定特定于用户的参数来生成这些图像时,我们无法缓存它们,但当生成的图像对每个用户都相同(例如,每天只更新的默认图表图像)时,我们可以将它们缓存1天,并避免每次用户请求这些“默认”类型图像时都重新创建图表图像的昂贵过程。
延伸阅读
许多其他作者比上述摘要更详细地探讨了这些内容,虽然10%的建议改进可以带来90%的性能效益,但我仍向任何有兴趣实现最快Web应用程序的人推荐以下链接:
- 加快您的网站速度的最佳实践
- 通过ASP.NET 2.0中改进的视图状态加快您的站点速度
- 编写高性能Web应用程序的10个技巧
- ASP.NET Ajax 深入性能分析
- 提升ASP.NET性能
- 10 ASP.NET 性能和可伸缩性秘诀
在我上述摘要中省略的许多详细项目里,我主要推荐使用内容分发网络(CDN)。我之所以省略它,是因为我们尚未实施,所以我只能猜测其性能提升,但对于大型全球网站来说,这理应是显著的。
最后
从小型本地Web应用程序到由团队而非个人开发的大型、可扩展且可能全球化的应用程序,这是一个漫长的旅程——我希望我所经历的这些过程的记录能对面临类似项目的其他开发人员或项目经理有所帮助。
附加的项目文件是文章开头讨论的整体解决方案架构和持久化类。如果对讨论的其他一些领域相关的代码感兴趣,请告诉我,我会看看能提供什么。
历史
- 2008年10月21日:初始版本