一、返回多个数据集
检查你的访问数据库代码,看是否存在着要返回多次的请求。每次往返降低了你的应用程序的每秒能够响应请求的次数。通过在单个数据库请求中返回多个结果集,可以减少与数据库的通信时间,使你的系统具有扩展性,也可以减少数据库服务器响应请求的工作量。
如果你是用动态的SQL语句来返回多个数据集,那我建议你用存储过程来替代动态的SQL语句。是否把业务逻辑写到存储过程中,这个有点争议,但是我认为,把业务逻辑写到存储过程里面可以限制返回结果集的大小,减少网络数据的流量,在逻辑层也不用再过滤数据,这是一个好事情。
用SqlCommand对象的ExecuteReader方法返回一个强类型的业务对象,再调用NextResult方法来移动数据集指针来定位数据集。示例一演示了一个返回多个ArrayList强类型对象的例子。只从数据库中返回你需要的数据可以大大的减少你的服务器所耗用的内存。
二、对数据进行分页
ASP.NET的DataGrid有一个非常有用的功能:分页。如果DataGrid允许分页,在某一时刻它只下载某一页的数据,另外,它有一个数据分页的齐览导航栏,它让你可以选择浏览某一页,而且每次只下载一页的数据。
但是它有一个小小的缺点,就是你必须把所有的数据都绑定到DataGrid中。也就是说,你的数据层必须返回所有的数据,然后DataGrid再根据当前页过滤出当前页所需要的数据显示出来。如果有一个一万条记录的结果集要用DataGrid进行分页,假设DataGrid每页只显示25条数据,那就意味着每次请求都有9975条数据都是要丢弃的。每次请求都要返回这么大的数据集,对应用程序的性能影响是非常大的。
一个好的解决方案就是写一个分页的存储过程,例子2是一个用于对Northwind数据库orders表的分页存储过程。你只需要传当前页码,每页显示的条数两个参数进来,存储过程会返回相应的结果。
在服务器端,我专门写了一个分页的控件来处理数据的分页,在这里,我用了第一个方法,在一个存储过程里面返回了两个结果集:数据记录总数和要求的结果集。
返回的记录总数取决于要执行查询,例如,一个where条件可以限制返回结果集的大小。因为在分页界面中必须要根据数据集的大小来计算总的页数,所以必须返回结果集的记录数。例如,如果一共有1000000条记录,如果用where条件就可以过滤成只返回1000条记录,存储过程的分页逻辑应该知道返回那些需要显示的数据。
三、连接池
用TCP来连接你的应用程序与数据库是一件很昂贵的事情(很费时的事情),微软的开发者可以通过用连接池来反复使用数据库的连接。比起每次请求都用TCP来连一次数据库,连接池只有在不存在有效的连接时才新建一个TCP连接。当关闭一个连接的时候,它会被放到池中,它仍然会保持与数据库的连接,这样就可以减少与数据库的TCP连接次数。
当然,你要注意那些忘记关的连接,你应在每次用完连接后马上关闭它。我要强调的是:无论什么人说.net framework中的GC(垃圾收集器)总会在你用完链接对象后调用连接对象的Close或者Dispose方法显式的关闭你的连接。不要期望CLR会在你想象的时间内关掉连接,虽然CLR最终都要销毁对象和关闭连接,但是我们并不能确定它到底会在什么时候做这些事情。
要用连接池优化,有两条规则,第一,打开连接,处理数据,然后关闭连接,如果你必须在每次请求中多次打开或关闭连接,这好过一直打开一个连接,然后把它传到各个方法中。第二,用相同的连接字符串,如你用基于登录用户的连接字符串,这将不能利用连接池的优化功能。如果你用的是集成的论证,因为用户很多,所以你也不能充分利用连接池的优化功能。.net CLR 提供了一个数据性能,它在我们需要跟踪程序性能特性的时候非常有用,当然也包括连接池的跟踪了。
无论你的应用程序什么时候要连在另一台机子的资源,如数据库,你都应该重点优化你连资源所花的时间,接收和发送数据的时间,以及往返回之间的次数,优化你的应用程序中的每一个处理点(process hop),它是提高你的应用性能的出发点。
应用程序层包含与数据层连接,传送数据到相应的类的实例以及业务处理的逻辑,如授权,更重要的,这里要完成缓存逻辑。
四、ASP.NET缓存API
在写应用程序之前,你要做的第一件事是让应用程序最大化的利用ASP.Net的缓存功能。
如果你的组件是要在Asp.Net应用程序中运行,你只要把System.Web.dll引用到你的项目中就可以了。然后用HttpRuntime.Cache属性就可以访问Cache了。(也可以通过Page.Cache或者HttpContext.Cache访问)
有以下几条缓存数据的规则,第一,数据可能会频繁的被使用,这种数据可以缓存,第二,数据的访问频率非常高,或者一个数据的访问频率不高,但是它的生存周期很长,这样的数据最好也缓存起来。第三是一个常常被忽略的问题,有时候我们缓存了太多数据,通常在一台X86的机子上,如果你要缓存的数据超过800M的话,就会出现内存溢出的错误。所以说缓存是有限的。换句话说,你应该估计缓存集的大小,把缓存集的大小限制在10以内,否则它可能会出问题,在Asp.net中,如果缓存过大的话也会报内存溢出错误,特别是如果缓存大的DataSet对象的时候。
这里有几个你必须了解的重要的缓存机制,首先是缓存实现了“最近使用”原则,当缓存少的时候,它会自动的强制清除那些无用的缓存,其次“条件依赖”强制清除原则。条件可以是时间,关键字和文件,以时间作为条件是最常用的。在asp.net 2.0中增加一更强的条件,就是数据库条件,当数据库中的数据发生变化时,就会强制清除缓存。
五、预请求缓存
在前面,我提到过即使我们只对某些地方作了一个小小的性能改进也可以获得大的性能提升,我非常喜欢用预请求缓存来提升程序的性能。
虽然Cache API 设计成用来保存某段时间的数据,而预请求缓存只是保存某个时期的某个请求的内容。如果某个请求的访问频率高,而且这个请求只需要提取,应用,修改或者更新数据一次,那么就可以预缓存该请求。
在CS的论坛应用程序中,每一个页面的服务器控件都要求得到用于决定它的皮肤的自定义的数据,以决定用哪个样式表及其它的一些个性化东西,这里面的某些数据可能要长时间的保存,有些时间则不然,如控件的skin数据,它只需要应用一次,而后就可以一直使用。
要实现预请求缓存,用ASp.net的HttpContext类,HttpContext类的实例在每一个请求中创建,在请求期间的任何地方都可以通过HttpContext.Current属性访问。HttpContext类有一个Items集合属性,在请求期间所有的对象和数据都被添加到这个集合中缓存起来。和你用Cache缓存访问频率高数据一样,你可以用HttpContext.Items缓存那些每个请求都要用到的基础数据。它背后的逻辑很简单:我们向HttpContext.Items中添加一个数据,然后再从它里面读出数据。
六、后台处理
通过上面的方法你的应用程序应该运行得很快了,是不是?但是在某些时候,程序中的一次请求可能要执行一个非常耗时的任务,如发送邮件或者检查提交数据的正确性等。
当我们把asp.net Forums 1.0集成在CS中的时候,发现提交一个新的帖子的时候会非常慢,每次新增一个帖子的时候,应用程序首先要检查这个帖子是不是重复提的,然后用“badword”过滤器来过滤,检查图片附加码,做帖子的索引,把她添加到合适的队列中,验证它的附件。最后,发邮件到它的订阅者邮件箱中,显然,这个工作量很大。
结果它把大量的时间都花在做索引和发送邮件中了,做帖子索引是一项很耗时的操作,而发送邮件给订阅者都需要连接到SMTP服务,然后每一个订阅者都发一封邮件,随着订阅用户的增加,发送邮件的时间会更长。
索引和发邮件并不需要在每次请求时触发,理想状态下,我们想要批量的处理这些操作,每次只发25封邮件或者每隔五分钟把所有的要发的新邮件发一次。我们决定使用与数据库原型缓存一样的代码,但是失败了,所以又不得不回到VS.Net 2005.
我们在System.Treading命空间下找到了Timer类,这个类非常有用,但却很少有人知道,Web开发人员则更少有人知道了。一旦他建立了该类的实例,每隔一个指定的时间,Time类就会从线程池中的一个线程中调用指定的回调函数。这意味着你的asp.net应用程序可以在没有请求的时候也可以运行这就是后以处理的解决方案,你可以让做索引和发邮件工作在后台运行,而不是每次请求的时候必须执行。
后台运行的技术有两个问题,第一是,当你的应用程序域卸载后,Timer类实例就会停止运行了。也就是不会调回回调方法了。另外,因为CLR的每个进程中都有许多的线程在运行,你将很难让Timer获得一个线程来执行它,或者能执行它。但会延时,Asp.net层要尽量少的使用这种技术,以减少进程中线程的数量,或者只让请求用小部分的线程,当然如果你有大量的异步工作的话,那就只能用它了。
七、页面输出缓存和代理服务
Asp.net是你的界面层,它包含页面,用户控件,服务器控件(HttpHandlers和HttpModules)以及它们生成的内容,如果你有一个Asp.net页面用来输出Html,xml,image或者是其他的数据,对每一个请求你都用代码来生成相同的输出内容,你就很有必要考虑用页面输出缓存了。
你只要简单的把下面的这一行代码复制到你的页面就可以实现了:<%@ PageOutputCache VaryByParams="none" Duration="60" %> 你就可以有效的利用第一次请求里生成的页面输出缓存内容,60s后重新生成一道页面内容。这种技术其实也是运用一些低层的Cache API来实现。用页面输出缓存有几个参数可以配置,如上面所说的VaryByParams参数,该参数表示什么时候触发重输出条件,也可以指定在Http Get或Http Post请求模式下缓存输出。例如当我们设置该参数为VaryByParams=“Report”的时候,default.aspx? Report=1或者default.aspx?Report=2请求的输出都会被缓存起来。参数的值可以是多个用分号隔开参数。
许多人都没有意识到当用页面输出缓存的时候,asp.net也会生成http头集(Http header)保存在下游的缓存服务器中,这些信息可以用于Microsoft Internet安全性中以及加速服务器的响应速度。当Http缓存的头被重置时,请求的内容会被缓在网络资源中,当客户端再次请求该内容时,就不会再从源服务器上获得内容了,而直接从缓存中获得内容。
虽然页面输出缓存不提高你的应用程序性能,但是它能减少了从服务器中加载已缓存页面内容的次数。当然,这仅限于缓存匿名用户可以访问的页面。因为一旦页面被缓存后,就不能再执行授权操作了。
八、用IIS6.0的Kernel Caching
如果你的应用程序没有运行在IIS6.0中,那么你就失去了一些很好的提高应用程序性能的方法。在第七个方法中,我讲了用页面输出缓存提高程序的性能的方法。在IIS5.0中,当一个请求来到IIS后,IIS会把它转给ASp.net ,当应用了页面输出缓存时,Asp.net中的HttpHandler会接到该请求,HttpHandler从缓存中把内容取出来并返回。
如果你用的是IIS6.0.它有一个非常好的功能就是Kernel Caching,而且你不必修改asp.net程序中任何代码,当asp.net借到一个已缓存的请求,IIS的