一个在互联网上的用户的请求的处理过程是这样的:
1. 首先经硬件负载均衡处理,选定一个Web服务器来响应这个请求,然后将该请求交给该服务器。
2. 此Web服务器执行所请求的页面,该页面的后端代码先查询缓存服务器,即调用缓存服务接口查询是否已经有缓存,如果有,就直接返回缓存的结果。
3. 如果缓存里没有就调用商务逻辑服务接口,进而调用商务逻辑服务。商务逻辑服务执行时,如果需要访问数据库,会先检查缓存中是否有缓存的数据库内容,如果有,就会用缓存的数据库内容来进行商务逻辑的计算。如果没有缓存,就会调用数据访问接口以存取数据。
4. 类似地,数据访问服务也会查看缓存,然后根据所要求的数据内容去访问相应的数据库,如果是只读的请求,数据访问服务可以将数据库访问请求发给做日志复制的数据库服务器。如果是写的请求,可以发给主数据库服务器。
5. 数据库服务器执行应用的Sql请求,返回结果。再由数据服务返回给商务逻辑服务。
6. 商务逻辑服务再返回给Web服务器,由Web服务器生成页面内容返回给互联网上的用户。
以上过程与Layered的架构类似,只是比Layered的架构多经过了几个服务接口。如果没有这些服务接口,因为UI Tier,商务逻辑Tier,数据访问Tier是在不同的服务器上的,它们根本就不能直接对话。因为它们是在不同的.net VM中的。它们必须得借助与这些服务接口才能互相之间进行调用。这些服务接口具体的组成技术可以是WCF,也可以是.net remoting,等。应该说目前最好的选择是WCF。
UI Tier
关于SessionState的技术方案
为了让应用程序具有可伸缩性,必须让每一Tier都有负载均衡的特性,也就是要做到用户的请求由任何一个同一Tier中的服务器来处理都不会有任何问题。关于用户Session的处理就必须有一个妥善的解决方案。有不少人不赞同采用SessionState,觉得SessionState对ASP.NET应用的性能影响比较大。还有人写文章说同一个SessionID的AcquireRequestState会在页面代码前获得对Session对象的锁,因此容易有较大的延迟,对性能影响不小。另外的人认为Session占用服务器的内存比较多,同时需要一些CPU资源来将Session中的对象序列化和反序列化。所以一种比较普遍的观点是不采用ASP.NET本身提供的Session机制。其实采用SessionState和不采用SessionState都各有特点。了解其特点后再做权衡取舍才比较合适。
完全不采用SesstionState
完全不采用SesstionState是在Web.config中写上<sessionState mode=”Off”/> 或者 <Pages enableSessionState=”Off”/>来禁止SessionState。那整个应用的所有页面都不会用SessionState。其实这不全面,http请求处理周期里还有一个系统默认的httpmodule在处理SessionState。还须在Web.config加一句:
<httpModules>
<remove name="Session" />
</httpModules>
应用程序里完全不采用ASP.NET本身提供的SessionState机制,但是应用的需求是要求应用程序有类似于Session的机制的。比如购物车的概念。记住用户选择了哪些商品,在用户点了买单时才处理用户选择了的商品。如果不用ASP.NET本身提供的SessionState机制,就必须自己实现一个Session机制。比如可以在数据库中有一张表来记录自定义的Session数据。如果用户浏览器支持cookie,可以用该cookie存储自定义的Session ID值。这个Session ID值用于到数据库中去查询存储的Session数据。如果用户浏览器不支持cookie,那么就可以在页面中放置隐藏的字段(hidden field)。此隐藏字段用于存储自定义的Session ID。还可以用URL中参数放一个Session参数的办法。这样获得的Session机制是自己管理的Session机制。需要将Session的创建,过时失效,查询Session数据,删除旧Session等都管理起来。
这样的自定义的Session机制将Session数据存储到了数据库。那么就可以不依赖与某一台具体的服务器。从而获得的可伸缩的特性。
采用SessionState
采用SessionState是ASP.NET默认的机制。ASP.NET的SessionState有几种模式。InProc,StateServer,SqlServer模式和自定义模式。InProc不支持负载均衡的场景。只有StateServer和SqlServer模式才支持。自定义模式是指我们自己实现Session数据的持久化,比如将Session数据放到Oracle数据库或者MySql数据库中,自定义模式也可以支持负载均衡。在StateServer和SqlServer模式时,放入Session中的数据都必须是能序列化的。建议采用SqlServer模式的Session机制。配置是这样的:
<system.web>
<sessionState mode=" Off | InProc | StateServer | SQLServer "
cookieless=" true | false "
timeout=" number of minutes "
stateConnectionString=" tcpip=server:port "
sqlConnectionString=" sql connection string "
stateNetworkTimeout=" number of seconds " />
</system.web>
Session采用了SqlServer模式之后,所有数据都会经序列化,并存储到SqlServer数据库中。采用这种模式的Session机制,其Session可以由任何一个UI Tier的服务器来处理,因为Session数据是存储在专门的数据库中的。如果是采用这种模式的Session机制,那么最好有专门的数据库服务器供存储Session数据。通过上述安排,ASP.NET应用就获得了负载均衡,可伸缩的能力。
采用了ASP.NET的SessionState的之后,同一个Session ID下的不同页面请求会有一定的制约。注意这里说的同一个Session ID下的不同页面。这就象数据库的锁机制一样。默认的ASP页面设置都是能对Session对象进行读和写。那么如果同一个Session ID的两个不同请求访问两个不同的页面,就会因为都去锁住Session对象,而造成有一个请求被阻塞较长时间,因为要等另一个请求处理完毕。有同仁可能觉得奇怪,怎么会有同一个Session ID请求两个不同的页面。其实这与页面中的iframe,frameset和AJAX技术有关。包含iframe, frameset的页面已经要存取Session了,iframe或者frameset里面的页面也要存取Session,就有可能造成一先一后,都是同一个Session ID,后面的页面被前面的页面锁住,直到前面的页面都处理完,释放对Session的锁,才能处理后面的页面。AJAX也类似。也存在这个问题。这个默认的机制所带来的延迟在小型的ASP.NET应用中可以不用理睬。但是在大型的ASP.NET应用中是必须解决的问题。要解决这个问题,只能从应用的角度尽力减少需要写Session的范围,即明确确定哪些页面需要读且写Session数据。还需要确定哪些页面是只需要读Session数据。另外还需要确定哪些页面不需要参与读或者写Session数据,即与Session数据无关的页面。通过这样的工作,就确定了Session的范围。对于需要读且写Session的页面,可以显示地在页面中写上< % @Page enableSessionState=”On”% >。对于只需要读Session的页面,可以写上< % @Page enableSessionState=”ReadOnly”% >。对于不需要Session的页面,可以写上< % @Page enableSessionState=”Off”% >。在一个iframe相关的所有页面中,不要所有的页面都去读写Session,这样就可以避免Session争锁所带来的延迟。AJAX所涉及的页面也是如此,尽可能地减少读写Session,发生这种Session争锁的延迟就会少一些。锁越少,整个UI Tier的处理能力就会越大。
关于ViewState的技术方案
ViewState使服务器控件可以在往返行程中重新填充它们的属性值,而程序员不需要编写任何代码。这些属性值包括可见的属性,也包括不可见的。可见的属性如Text属性,不可见的是某些控件的ControlState。ControlState是比较特殊的内容,它总是存储在ViewState字段中。即使用EnableViewState="false"禁止了ViewState,ViewState字段还是有一些内容,这些内容就是ControlState。
曾经听到不少人抱怨说ViewState大,有时光ViewState就几百K。一个页面的HTML,很大的部分是ViewState占用了。微软的文章也在说不需要ViewState的地方就禁止ViewState。所以合理决定应用程序哪些地方需要ViewState。毕竟ViewSate也一定程度上带给程序员一些方便。禁止ViewState是可以在整个应用的级别,页面的级别,和控件的级别来禁止。整个应用的级别禁止ViewState: <pages enableViewState="false" enableViewStateMac="false" enableEventValidation="false"></pages>,页面的级别如:< % @ Page EnableViewState="false" % >,控件的级别如:<asp:datagrid EnableViewState="false" datasource="..." runat="server"/>。禁止了ViewState之后,页面中的__ViewState字段已经大大减小了,但是还是存在。上面已经提到了,__ViewState字段里剩下的内容就是ControlState的。如果想让__ViewState字段没有内容,可以改写Page类的此两方法:
protected override void SavePageStateToPersistenceMedium(object viewState)
{
}
protected override object LoadPageStateFromPersistenceMedium()
{
return null;
}
这样__ViewState字段就完全没有内容了。当然我们可以在此两方法里面设计出自己的持久化ViewState内容的方案。比如将ViewState持久化到缓存中去,或者持久化到SqlServer中去。那么ViewState的内容就不再需要发送的到用户浏览器中了。上面介绍了一些在某些地方禁用ViewState的方法。下面就由开发者和用户来决定哪些页面或者控件需要ViewState,还是完全不要ViewState。ViewState机制具有两面性,一方面方便了程序员,另一方面可能对性能造成影响。所以要小心对待。
减少与服务器的交互次数和不必要的服务器端处理
Page.IsPostBack
Page.IsPostBack可以判断是否有Form提交。第一次访问时的处理和有Form提交的处理是不一样的。这样可以避免不必要的服务器端处理。
AutoPostBack属性
许多服务器端控件都有AutoPostBack,能禁止的都禁止了。
多做客户端的数据验证
用户在浏览器里面的输入,尽量先用客户端JavaScript验证处理,等通过了再提交给服务器。这样减少向服务器提交请求的次数。
AJAX的请求量进行控制
AJAX带来了很炫的效果,但是能适当地减少调用AJAX调用次数,比如能否合并AJAX的调用。
用Server.Transfer不用Response.Redirect
Server.Transfer发生在服务器端,而Response.Redirect发生在用户浏览器中。会多一次HTTP请求。
去除不必要的默认httpModule
如不要SessionState,不要WindowsAuthentication,不要PassportAuthentication等等:
<httpModules>
<remove name="Session" />
<remove name="WindowsAuthentication" />
<remove name="PassportAuthentication" />
<remove name="AnonymousIdentification" />
<remove name="UrlAuthorization" />
<remove name="FileAuthorization" />
</httpModules>
设置processModel
手动设置processModel参数中的MaxWorkerThreads 和 MaxIOThreads 属性,通过观察效果带调整参数。如果机器资源允许,可以稍微多点。
设置Web garden
只要服务器资源允许,就可以建立Web garden,在同一个服务器上多开几个工作者进程。32位Windows上一个进程通常只能占用2G-3G内存(因为高地址的2G或者1G是Windows本身用来装配系统文件用的)。64位Windows上一个进程能占用的内存相对32位大一点,但是服务器有比如100多G的内存,可以适当多开几个工作者进程。这可以增加单台服务器的处理能力。要设置Web garden可以先在IIS管理器里面找到对应的应用程序池,在查看该应用程序池的高级属性,再找到最大工作者进程参数,见图。