性能调优方法
由于现代应用程序的复杂性以及缺乏正式的调整方法,性能调整企业Java应用程序可能是一项艰巨的任务,有时甚至是徒劳的。 现代企业应用程序与十年前的同类应用程序有很大不同,因为它们支持多个输入,多个输出以及复杂的框架和业务处理引擎。 十年前,基于Web的企业应用程序可能期望来自Web浏览器的输入,通过与数据库或旧系统的交互进行的后端处理以及输出回Web浏览器(HTML)的期望。 如今,输入可以采用HTML浏览器,胖客户端,移动设备或Web服务的形式,它们可以通过运行在十二种不同体系结构之一或门户容器中的servlet传递,这些容器又可以称为企业bean,外部Web服务或将处理委托给业务规则引擎。 然后,这些组件中的每一个都可以与内容管理系统,缓存层,大量数据库和旧版系统进行交互。 然后,通常将输出包含在与表示无关的形式中,然后将其转换为HTML,XML,WML或客户端应用程序期望的任何其他格式。 与过去相比,现代应用程序具有更多的活动部件和更多的“黑匣子”,这对性能调优提出了重大挑战。
除了复杂性的增加外,性能调整还比“科学”更“艺术”,大多数性能调整指南都侧重于有时是神秘的,可能会或可能不会影响最终用户体验的性能指标。 本文试图通过提出一种可重复的过程,将性能调优过程过渡到“科学”领域,该过程通过根据“等待点”或应用程序的某些部分来分析应用程序的体系结构,重点关注最终用户体验。导致请求等待。 简而言之,基于等待的调整使性能工程师可以通过优化最终用户体验来快速实现可衡量的性能提升。
性能调整过程
在审查基于等待的调优和等待点分析的详细信息之前,本节介绍有效的性能调优过程的概述或路线图。 性能调整可以简单地分为四个步骤:
- 负载测试
- 容器调优
- 应用程序调优
- 重复
与大多数计算机科学一样,性能调整是一个反复的过程。 首先要构建一个适当的负载测试,其中要包含平衡的和代表性的服务请求,并通过容器调整练习来满足。 随着容器的调整,由于增加的负载而将出现应用程序瓶颈。 识别并解决应用程序瓶颈后,应用程序的行为将有所不同,这将需要重新调整容器。 可以重复这种在容器和应用程序调整之间交替的过程,直到性能可以接受(或直到项目用完时间并需要发布为止)为止。
负载测试方法
能够进行性能调整的先决条件是构建适当的负载测试套件。 负载测试必须解决以下两点:
- 负载必须代表最终用户在做什么(或预期要做什么)
- 必须以与模拟最终用户行为相同的比例来平衡负载
也就是说,负载必须以与最终用户执行动作相同的比例来再现最终用户动作。 为了说明平衡最终用户操作的重要性,请考虑以下情形:在保险索赔部门中,员工表现出以下行为:
- 用户在上午8点登录
- 他们平均每天早上处理5项索赔
- 大约80%的用户忘记在午餐前注销,因此会话期满
- 午餐后,用户重新登录到应用程序
- 他们平均每天下午处理五次索偿
- 他们在离开前会生成两个报告
- 80%的用户在回家之前从系统注销
该示例可能是对实际应用程序的过度简化,但足以在服务请求之间建立平衡。 此方案具有以下平衡:两次登录,十次声明,两个报告和一个注销。
如果负载生成器在不同的服务请求之间平均分配负载,将会发生什么情况? 在这种情况下,用户登录和注销功能将收到与索赔处理功能相同的负载量。 考虑到预期有1000个同时用户的负载,登录功能可能会很快瓦解,并导致组织投入资金来构建可以处理永远不会收到的负载的登录组件。 更糟糕的是,调优工作着重于调优登录功能,这在这种情况下是最大的瓶颈,但是却以丢失索赔处理功能为代价。 简而言之,不平衡的负载可能导致调整应用程序的某些部分以支持它们将永远不会收到的负载,而不会调整应用程序的其他部分以支持它们将要收到的负载!
检查现有应用程序(或现有应用程序的新版本)与构建新应用程序时,确定平衡的负载和代表该应用程序的方法有所不同。
现有应用
现有应用程序比新应用程序具有明显的优势:可以在生产环境中观察到真实的用户行为。 根据请求的性质以及应用程序如何识别它们,有两种方法可以识别最终用户的行为:
- 访问日志
- 最终用户体验监控器
对于大多数基于Web的应用程序,访问日志可提供足够的洞察力,以帮助发现服务请求的性质及其相对平衡。 可以将Web服务器配置为捕获最终用户请求信息,并将其存储在称为“访问日志”的日志文件中(因为该文件通常称为“ access.log”。)能够使用访问日志的关键识别用户行为的原因是应用程序交互需要通过其URI加以区分。 例如,如果上一个示例中的动作等同于URI,例如“ /login.do”、“/processClaim.do”和“ /logout.do”,那么在访问日志文件中查找这些动作将非常简单。确定他们的相对平衡。 此外,按访问频率最高的URI排序访问日志文件将快速识别出请求的前“ n”个百分比,其中“ n”应为80%左右。
访问日志是文本文件,可以手动检查(不是非常有成果的任务),可以以编程方式进行解析或可以通过工具进行分析。 有许多商业解决方案,但是Quest Software拥有一种名为Funnel Web Analyzer的产品,该产品已于几年前淘汰,但由于需求旺盛,他们将产品更新为免费软件。 Funnel Web Analyzer可以分析大多数访问日志文件,并提供构建适当的负载测试所需的信息。
某些应用程序不是那么简单,并且用户交互无法通过简单的URI轻松识别。 例如,考虑一个应用程序,该应用程序具有一个接受XML有效载荷的前端控制器servlet,并且要处理的业务逻辑包含在有效载荷内部。 在这种情况下,需要使用另一种工具来检查该有效负载以确定满足的业务案例。 这可能使用Servlet筛选器手动构建,或者可能需要称为最终用户体验监视器的硬件设备。
无论如何获得用户行为,这都是开始执行任何性能调优练习之前的核心先决条件。
新的应用
新应用程序提出了一个独特的挑战,因为没有任何最终用户的行为需要分析。 识别新应用程序中的用户行为时,需要考虑三个步骤,如图1所示。
图1估计新应用程序的最终用户行为
第一步是估计最终用户将要做什么。 此步骤是表达“猜测”的正式方式,但是有根据的猜测。 估算应来自两方之间的讨论:应用程序业务所有者和应用程序技术所有者。 应用程序业务所有者(通常是产品经理)负责详细说明最终用户应如何使用该应用程序-例如,他可能会报告最终用户应登录,处理五个声明,超时,处理五个更多索赔,生成两个报告,然后注销。 应用程序技术所有者(可能是架构师或技术负责人)负责将抽象的业务交互列表转换为生成负载测试所需的技术步骤-例如,他可能会报告通过“ / login”完成登录。 URI,并且有五个URI构成了处理索赔的步骤。 这些人(或某些大型项目中的小组或委员会)在一起应提供足够的信息以构建基准负载测试。
创建负载测试并将其用于调整应用程序和容器并将应用程序部署到生产环境之后,调整工作将无法完成。 这种负载测试方法的下一步是验证负载测试套件。 这通常是一个多阶段的活动:
- 冒烟测试验证:在运营的第一周或第二周,根据现场生产最终用户的行为验证估计值。 需要执行此验证步骤,以确保在估计过程中不会发生任何重大错误。
- 生产验证:某些应用程序需要时间才能使用户陷入一致的使用模式。 此时间量取决于应用程序,可能需要一个月或一个季度,但是在用户适应使用该应用程序后,根据估计值验证最终用户的行为很重要。
- 回归验证:最佳做法是在应用程序的整个生产生命周期中定期验证用户行为,以防用户行为发生变化或引入了更改最终用户行为的新功能或新工作流。
通常被忽略的最后一步是反射 。 重要的是要反思针对实际最终用户行为的估计准确性,因为只有通过反思,才能更好地理解用户行为,并在后续应用中改善估计。 没有反思,就会一次又一次地犯同样的错误,最终将增加调优工作量。
基于等待的调整
进行负载测试后,就可以确定最适合在哪里进行调整的时候了。 大多数调优指南都关注“性能比率”或指标之间的关系。 例如,调整指南可能会强调高速缓存命中率应为80%或更高,因此在调整高速缓存大小之前,对应用程序进行负载测试,直到命中率达到80%。 然后,移至列表中的下一个指标,同时不断地验证调整新指标不会使先前指标的调整无效。
这不仅是一项艰巨的任务,而且还可能徒劳无功。 例如,将高速缓存命中率调整为80%可能是一件好事,但是还有一些更重要的问题,例如:
- 应用程序对缓存的依赖性如何(与缓存交互的请求百分比)?
- 这些请求相对于应用程序中的其他请求有多重要?
- 被缓存的项目的性质是什么? 是否应该完全缓存它们?
基于等待的调整提倡了以下概念:分析应用程序的业务交互,实现那些业务交互的基础体系结构以及优化那些业务交互的处理。 第一步是分析应用程序的体系结构,以识别用于满足请求的技术。 每种采用的技术都可以提供一个“等待点”,或者在应用程序中某个位置,在该位置,请求可能必须等待某些东西才能继续处理。 例如,如果请求执行数据库查询,那么它必须从连接池中获取数据库连接–如果连接池没有可用的连接,则该请求必须等待连接变为可用。 同样,如果请求调用一个Web服务,则该Web服务将具有一个请求队列(带有用于处理传入请求的关联线程池),这有可能导致请求在线程变为可用之前等待。
通过这种称为等待点分析的分析,可以确定两类等待点:
- 基于层的等待点
- 基于技术的等待点
本节首先回顾等待点体系结构分析,然后调查各种类型的等待点。
等待点架构分析
讨论最重要的是,必须在要调整的应用程序体系结构的上下文中执行性能调整。 这是调整性能比率如此无效的原因之一:将任意性能指标调整为最佳实践设置可能对调整的应用程序有好处,也可能没有好处,并且可能会或不会对最终用户的体验产生积极影响。
等待点分析是通过应用程序剖析主要请求处理路径的过程,以识别可能导致请求等待的资源。 在等待点分析练习中采用的最有效策略是,确定应用程序中的核心处理路径,并在白板上使用这些路径。 包括请求之间可能传递的所有层,请求可能与之交互的所有外部服务,池中的所有对象以及缓存中的所有对象。
基于层的等待点
每当请求跨物理层(例如,在Web层和业务层之间)传递或对外部服务器进行调用(例如,在调用Web服务时)时,都存在与该转换关联的隐式等待点。 考虑一下,如果服务器一次只服务一个请求,那么服务器将无效,因此它们实现了多线程策略。 通常,服务器在套接字上侦听传入的请求-接收到请求后,它将服务器Swift将该请求放入请求队列,然后返回侦听其他传入的请求。 然后,请求队列具有一个关联的线程池,该线程池从队列中删除请求并对其进行处理。 图2说明了如何通过三个层执行此过程:Web服务器,动态Web层和业务层。
图2基于层的等待点
因为跨层传递的请求的操作涉及一个请求队列,该队列由关联的线程池提供服务,所以线程池提供了潜在的重要等待点。 必须根据以下注意事项调整每个线程池的大小:
- 池必须足够大,以便传入的请求不必不必要地等待线程
- 池不能太大,以至于会使服务器饱和。 太多线程将导致服务器花费更多时间在各个线程上下文之间进行切换,而花费较少的时间处理请求。 这通常表现为CPU利用率高和请求吞吐量降低
- 池的大小应最佳,以免饱和与之交互的任何后端资源。 例如,如果一个数据库只能支持来自单个服务器的50个请求,则该服务器向该数据库发送的请求不应超过50个。
服务器线程池的最佳大小是在其有限依赖关系上生成足够负载的线程数,以最大程度地利用它们,而又不会使其饱和。 请参阅下面的“向后调整”部分,以了解有关限制依赖项池大小的更多信息。
基于技术的等待点
虽然基于层的等待点与在服务器之间移动请求有关,但是基于技术的等待点与在单个服务器的内部工作中有效地移动请求有关。 基于层的调整在某种程度上类似于IBM的Queue Tuning ,是调整应用程序的有效第一步,但是如果忽略调整应用程序服务器的内部工作,则会对应用程序的性能产生巨大影响。 这类似于调整JDBC连接池以将最理想的负载发送给数据库,但是却忽略了正在执行SQL的检查–如果查询将十个表连接在一起,每个表具有一百万条记录,则最佳负载可能是两个连接,但是如果查询得到优化,则数据库可能能够支持两百个连接。
查看应用程序服务器内部以及应用程序可以利用的潜在技术会产生以下基于技术的常见等待点:
- 池化对象(例如无状态会话Bean或应用程序池化的任何业务对象)
- 缓存基础架构
- 永久存储或外部依赖项池
- 消息基础架构
- 垃圾收集
在大多数情况下,除非应用程序服务器不正确地手动配置了无状态会话Bean池大小,否则应用程序服务器会优化无状态会话Bean池大小,并且不会出现明显的等待点。 但是,在应用程序池中存储的对象必须手动调整大小,并且这些对象可以显示有效的等待点。 考虑到当应用程序需要池化资源时,它必须从池中获取该资源的实例,使用它,然后将其返回到池中。 如果池的大小太小而所有对象实例都在使用中,则将强制一个请求以等待对象变为可用。 等待池资源会增加响应时间(显然),但是如果越来越多的请求继续备份池资源上的等待,则可能导致性能显着下降。 另一方面,如果池的大小太大,则可能会占用过多的内存,并对JVM的整体性能产生负面影响。 这是一个折衷方案,这些池的最佳大小只有在对池使用情况进行彻底分析之后才能确定。
池化对象是无状态的,这意味着应用程序从池中获取哪个实例都没有关系-任何实例都足够。 另一方面,缓存的对象是有状态的,这意味着当应用程序从缓存请求一个对象时,它需要一个特定的实例。 一个非常粗略的类比可以说明这种差异,这是:考虑很多人一天发生的两种常见活动:在超市购物,然后从学校接孩子。 在超市,任何收银员都可以结帐任何顾客,顾客选择哪一个收银员都无所谓。 因此,收银员将被集中起来。 但是,当父母从学校接孩子时,父母要他或她的孩子,另一个孩子是不够的。 因此,子级将被缓存。
话虽如此,缓存提出了独特的调优挑战。 从简单的角度来看,高速缓存的目的是将对象本地存储在内存中,并使它们易于应用程序使用,而不是按需获取它们。 适当大小的缓存比通过远程调用加载对象可以显着提高性能。 但是,缓存大小不正确会造成严重的性能障碍。 因为高速缓存保存有状态对象,所以对于高速缓存而言,重要的是要在高速缓存中维护最常访问的对象,并在高速缓存中提供足够的额外空间以使不频繁访问的对象能够通过。 考虑访问大小过小的缓存的请求的行为:
- 该请求检查缓存中是否有对象,但该对象不在缓存中
- 然后,该请求需要查询外部资源以获取对象的数据,并根据该数据构建对象
- 由于缓存通常是指维护最近访问的数据,因此需要将新项添加到缓存中(现在正在访问它)
- 但是,如果缓存已满,则必须使用“最近最少使用”算法之类的算法从缓存中选择一个对象以将其删除
- 如果缓存的对象的状态未持久到外部资源,则必须在丢弃对象之前更新外部资源
- 现在可以将新对象添加到缓存中
- 新对象最终可以返回给请求
这是一个繁琐的过程,如果大多数请求必须执行这些步骤中的每一个,则缓存将真正阻碍性能。 高速缓存的大小必须足够大,以最大程度地减少高速缓存的“缺失”,在这种情况下,未命中实质上等同于执行上述七个步骤中的每个步骤,但又不能太大以至于占用过多的JVM内存。 如果缓存需要足够大才能有效,那么重新考虑要缓存的对象的性质以及是否应该缓存它们就很重要。
与对象池类似,外部资源池(例如数据库连接池)的大小必须足够大,以使请求不会被迫等待池中的连接可用,但又不能太大,以至于应用程序会使外部资源饱和。 下面的“向后调整”部分讨论了如何确定这些池的最佳大小,但是在本部分的上下文中,请注意它们提供了另一个重要的等待点。
调整消息传递基础结构远远超出了本文的范围,主要产品(例如MSMQ,MQSeries,TIBCO等)之间的实现方式差异很大,但是请注意,如果应用程序正在与消息传递服务器进行交互,则必须对其进行适当的调整。或者它也可以提供一个等待点。
可能严重影响JVM性能的最后等待点是垃圾回收。 它不太适合本文中描述的等待点分析过程(检查请求以识别可能导致请求等待的技术),但是由于它会对性能产生如此深远的影响,因此将其列出这里。 不同的JVM实现和不同的垃圾收集策略会影响垃圾收集的执行方式,但是在许多情况下,主要垃圾收集(或标记清除紧凑垃圾收集)会导致整个JVM冻结,直到垃圾收集完成为止。 可以对JVM进行的最大的性能改进之一就是优化其垃圾回收行为。 有关垃圾回收的更多信息,请参加有关应用程序基础结构调整的GeekCap讨论 。
向后调整
现在已经调出了所有基于层和基于技术的等待点,最后一步是优化每个等待点的配置。 此步骤有时称为“向后调整”,从概念上讲非常简单:
- 打开所有基于层的等待点和外部依赖关系池,换句话说,将它们配置为允许过多的负载通过服务器
- 针对应用程序生成平衡且具有代表性的服务请求
- 确定首先饱和的等待点,这些等待点通常是外部依赖项,例如数据库
- 拧紧限制等待点的配置,以允许足够的负载传递给外部依赖关系而不会使其饱和
- 调整所有其他基于层的等待点,以仅通过服务器发送足够的负载以最大化限制的等待点,而不会导致请求等待
- 允许所有其他请求在业务逻辑精简层(例如Web服务器)上等待
此处的原则是,应用程序应仅将负载量发送到其外部依赖关系以最大化其使用量,而不会引起饱和-并且所有其他等待点应配置为仅传递足够的负载以最大化限制的等待点。 例如,如果一个数据库被来自每个应用程序服务器的50个连接饱和,则应将数据库连接池配置为向数据库发送少于50个请求(例如,将该池配置为包含40个或45个连接。) 80个线程生成40个数据库连接,然后应将应用程序的线程池配置为80。最后,Web服务器在任何给定时间向每个应用程序服务器发送的请求不应超过80个。
应该调整所有技术等待点,例如对象池,缓存和垃圾回收,以最大程度地提高请求的吞吐量,以便它可以尽快通过服务器或基于层的等待点之间传递。
摘要
性能调优曾经比“科学”更“艺术”,但是在将抽象分析和反复试验相结合之后,基于等待的调优已被证明可以使演习更加科学,更加有效。 基于等待的调优始于对应用程序体系结构执行等待点分析,以识别该体系结构采用的可能导致请求等待的技术。 等待点有两种形式:基于层的等待点(指示应用程序层之间的任何转换)和基于技术的等待点(例如缓存,池和消息传递基础结构等技术功能,可以改善或降低应用层之间的转换)。妨碍表现。 在确定了一组等待点的情况下,通过打开所有基于层的等待点和外部依赖关系池,针对应用程序生成平衡的和有代表性的负载,向后调整或收紧等待点以最大化性能来实现调整过程。一个请求的最弱链接,但不会使其饱和。
基于等待的调优已经一次又一次地证明了其在实际生产环境中的有效性,不仅如此,而且使性能工程师能够Swift实现可衡量的性能改进。
性能调优方法