Web 应用程序的运行方式以及如何提高性能了解Web应用程序的运行方式以及如何提高性能文档选项级别:中级Sean A.Walberg,高级网络工程师,P.Eng 2009年4月13日了解Web应用程序的各种组件如何交互,以及在哪些地方可能发现性能瓶颈。
开发人员和管理员都可以从本文受益,因为获得更好的性能是他们的责任。
动态的Web 应用程序能够存储大量信息,让用户能够通过熟悉的界面立即访问这些信息。
但是,随着应用程序越来越受欢迎,可能会发现对请求的响应速度没有以前那么快了。
开发人员应该了解Web应用程序处理Web请求的方式,知道在Web应用程序开发中可以做什么,不能做什么,这有助于减少日后的麻烦。
静态的Web请求(比如图1所示的请求)很容易理解。
客户机连接服务器(通常通过TCP端口80),使用HTTP协议发出一个简单的请求。
服务器解析这个请求,把它映射到文件系统上的一个文件。
然后,服务器向客户机发送一些描述有效负载(比如网页或图像)的响应头,最后向客户机发送文件。
在上面的场景中可能出现几个瓶颈。
如果请求的变化很大,导致无法有效地使用操作系统的磁盘缓存,那么服务器的磁盘会很忙,到了某种程度之后,就会减慢整个过程。
如果为客户机提供数据的网络通道饱和了,就会影响所有客户机。
但是,除了这些状况之外,"接收请求,发送文件"过程还是相当高效的。
通过做一些假设,可以大致体会静态服务器的性能。
假设一个请求的服务时间是10ms(主要受到磁头寻道时间的限制),那么大约每秒100个请求就会使磁盘接近饱和(10msec/request/1 second=100 requests/second)。
如果要发送10K的文档,就会产生大约8mbit/sec的Web通信流(100 requests/second*10KBytes/request*8bits/byte)。
如果可以从内存缓存中获取文件,就可以降低平均服务时间,因此增加服务器每秒能够处理的连接数。
如果您有磁盘服务时间或平均请求延时的真实数据,可以把它们放进上面的算式,从而计算出更准确的性能估计值。
既然服务器的处理容量是平均请求服务时间的倒数,那么如果服务时间加倍,服务器的处理容量(每秒处理的连接数)就会减半。
请记住这一点,下面看看动态应用程序的情况。
动态应用程序的流程依赖于应用程序的具体情况,但是一般情况下与图2相似。
与前一个示例中的客户机一样,图2中的客户机首先发出一个请求。
静态请求和动态请求之间实际上没什么差异(有时候.php或.cgi等扩展名可能意味着动态请求,但是它们可能引起误解)。
如何处理请求是由Web服务器决定的。
在图2中,请求被发送到一个应用服务器,比如运行一个Java应用程序的Solaris系统。
应用服务器执行一些处理,然后向数据库查询更多的信息。
得到这些信息之后,应用服务器生成一个HTML页面,这个页面由Web服务器转发给客户机。
因此,这个请求的服务时间是几个部分的总和。
如果数据库访问花费7ms,应用服务器花费13ms,Web服务器花费5ms,那么网页的服务时间就是25ms。
根据前面介绍的倒数规则,各个组件的容量分别是每秒142、77和200个请求。
因此,瓶颈是应用服务器,它使这个系统每秒只能处理77个连接;超过这个数量之后,Web 服务器被迫等待,连接开始排队。
但是,一定要注意一点:因为系统每秒只能分派77个连接,而一个连接需要的处理时间是25ms,所以并非每个应用程序用户的请求都能够在25ms内得到处理。
每个组件每次只能处理一个连接,所以在高峰负载下,请求不得不等待CPU时间。
在上面的示例中,考虑到排队时间和25ms的处理时间,平均请求服务时间最终会超过1.1秒。
关于解决这些排队问题的更多信息,请参见。
通过这些示例可以得出以下结论:在用户发出请求和获得最终页面之间的步骤越多,整个过程就越慢,系统容量就越低。
随着页面请求速率的增加,这种效应会越来越显著。
在项目开始时做出的体系结构决策也会影响站点处理负载的能力。
本文的其余部分将深入讨论这些问题。
应用程序(包括Web应用程序)的体系结构常常按照层来描述。
静态站点可以被看作只有一层--Web服务器。
如果用Web服务器运行某种脚本语言(比如PHP),从而连接数据库,那么这可以看作两层。
前一节中的示例有三层,即前端Web服务器、应用服务器和数据库。
一个软件也可能由多层组成,这取决于您谈话的对象。
例如,PHP脚本可能使用一个模板引擎把业务逻辑与表示分隔开,它可以被看作单独的两层。
Java应用程序可能通过Java servlet执行表示任务,servlet通过与Enterprise JavaBean(EJB)通信执行业务逻辑,EJB通过连接数据库获取更多信息。
因此,换一个角度来看,三层体系结构可能是另一副样子,尤其是在涉及不同的工具集时。
尽管应用程序的体系结构各不相同,但是有一些常见的体系结构趋势。
在一般情况下,应用程序需要四个功能层:客户机层表示层业务逻辑层数据层在Web应用程序中,客户机层由Web浏览器处理。
浏览器显示HTML并执行Javascript(以及Java applet、ActiveX或Flash applet),从而向用户显示信息和收集用户信息。
表示层是从服务器到客户机的接口,它负责控制输出的格式,让输出可以在客户机上显示。
业务逻辑层实施业务规则(比如计算和工作流),从而驱动应用程序。
最后,数据访问层是持久化的数据存储,比如数据库或文件存储。
大多数应用程序需要所有这四层的功能,尽管它们可能不需要明显完整地实现这些层。
另一种流行的体系结构是Model-View-Controller,这是一种用于分隔应用程序组件的模式。
在MVC模式中,模型封装业务逻辑层,并与框架一起封装数据层。
视图负责处理发送给客户机的数据表示。
控制器的作用是控制应用程序流程。
扩展Web应用程序的容量意味着让它能够处理更多的通信流。
容量扩展的一个方面是如何根据需求部署硬件。
另一个方面是应用程序如何响应新的硬件环境。
从概念上说,在出现性能问题时,往往首先想到使用功能更强的服务器;但是应用程序本身很可能造成其他瓶颈。
把应用程序划分为一系列层有助于收缩问题的范围,可以简化容量扩展。
现在先不考虑应用程序瓶颈。
扩展应用程序的硬件通常有两种方式:水平扩展和垂直扩展。
水平扩展意味着在一层中添加更多的服务器。
在前面的示例中,应用服务器的瓶颈把请求速率限制在每秒77个请求,通过添加第二个应用服务器并在两个服务器之间共享负载,可能可以解决此问题。
这会把理论容量提高到每秒154个请求,瓶颈位置就会转到数据库。
另一方面,垂直扩展意味着使用功能更强的计算机。
可以使用功能更强的计算机运行应用服务器的两个实例,或者更快地处理请求。
初看上去,您可能会完全排除垂直扩展方式,因为购买多台小型计算机通常比不断购买更高级的服务器便宜。
但是,在许多情况下,垂直扩展是更好的方法。
如果您有通过逻辑分区(LPAR)支持硬件分区的IBM Power服务器,就可以把空闲的容量添加到应用服务器层。
应用程序的需求也可能促使您选择垂直扩展。
在一台服务器上很容易通过共享内存段共享用户的会话状态。
如果使用两台服务器,就需要通过其他方式共享状态,比如数据库。
数据库访问比内存访问慢,所以两台服务器的处理速度达不到一台服务器的两倍。
数据库是另一个常常适合使用垂直扩展的场合。
让数据集跨越不同的服务器需要在应用程序层做大量工作,比如跨两个数据库联结列并确保数据是一致的。
使用更强大的数据库服务器要容易得多,而且不需要通过重新构建应用程序来支持分散的数据。
根据前面对应用程序体系结构的讨论可以看出,Web请求会通过多个阶段,每个阶段花费一定的执行时间。
请求排队通过每个步骤,完成一个步骤之后,再排队进入下一个步骤。
每个步骤很像人们在商店里排队结帐的情况。
可以把Web应用程序建模为一系列步骤(称为"队列")。
应用程序的每个组件都是一个队列。
建模为一系列队列的典型WebSphere应用程序如图3所示。
图3显示请求等待Web服务器处理它们,然后等待Web容器,依此类推。
如果进入某个队列的请求速率超过了此队列处理请求的速率,请求就会聚集起来。
当出现请求聚集时,服务时间是不可预测的,用户会察觉到浏览器会话延迟。
图3中的队列代表最糟糕的情况,因为Web服务器可以自己处理一些请求,即不需要访问数据库。
队列在UNIX环境中很常见。
当应用程序发出磁盘请求的速率快于磁盘返回数据的速率时,操作系统会让磁盘请求排队,还可能调整请求的次序以降低寻道时间。
另一个队列是运行队列,其中包含等待运行的进程的有序列表。
应用程序会等待轮到它们使用某些有限的资源(比如CPU)。
因此,队列调优是一种平衡的艺术。
队列太小,就会在仍然有富余容量的情况下拒绝用户。
队列太大,就会试图为过多的用户提供服务,导致性能很差。
导致情况更复杂的另一个因素是,这些排队位置并不是无成本的。
保留排队位置会导致内存开销,对于应用服务器,这会与正在处理请求的线程争用内存。
因此,在一般情况下,在应用服务器上排队并不是好方法。
推荐的方法是在应用服务器之前(比如在Web服务器上)排队。
这意味着Web服务器要保持与Web客户机的连接,并在应用服务器空闲时发出请求。
应用服务器只需处理它能够及时派发的请求。
IBM的文档中推荐了Web应用程序布局方法和各种队列的调优方法。
但是注意,IBM建议应该避免在WebSphere中排队。
这意味着应该把发送给WebSphere应用服务器的请求速率控制在能够立即处理的范围内。
Web服务器(或Web服务器前面的代理服务器)应该限制过多的连接,让它们等待处理。
这确保负载比较重的应用服务器队列能够把时间花在为有限的请求提供服务上,而不是试图同时为所有请求提供服务。
作为开发人员,应该按照一些一般原则提高应用程序的可伸缩性。
这些原则可以应用于大多数Web应用程序。
应用程序应该以某种方式向收集系统提供度量值(即使收集系统仅仅是日志文件)。
这些度量值包括访问应用程序中某个函数的频率或处理一个请求花费的时间等。
这并不会使应用程序运行得更快,但是有助于了解应用程序为什么会变慢以及代码的哪些部分花费的时间最长。
了解什么时候调用某些函数,这有助于把在系统上观察到的现象(比如CPU忙或磁盘活动量高)与应用程序中的活动(比如上传图像)联系起来。
能够了解站点上发生的情况,这是扩展站点容量的关键。
您认为不够优化的代码部分可能不会造成问题。
只有通过适当的度量,才能发现真正的瓶颈。
Web在本质上是无状态的。
用户发出的每个请求都独立于以前的请求。
但是,应用程序常常是有状态的。
用户必须登录应用程序以证明自己的身份,在访问站点期间可能要维护购物车的状态,还可能要填写供以后使用的个人信息。