新闻动态

—个典型的破坏性能测试性能测试是企业应用中最常见的

2019-03-12 21:55:43 作者:小麦

性能测试是企业应用中最常见的“破坏”之一。从用户的角度来看,性能的定义其实很简单,就是系统晌应用户请求的及时性。为了确保系统在客户的生产环境(基于特定的硬件、网络、业务数据和用户数量等条件)中达到及时性的要求,性能测试应运而生。

性能测试总是与性能调优相伴相随。性能测试是为了优化系统的性能、降低系统的响应时间、提高系统的信息吞吐量,反之,性能调优的成果必须通过性能测试来验证。

关于性能测试和性能调优的各种定义、公式、术语和工具,读者可以去查阅相关的参考资料。本节的目标是,尝试从实践中找到一些性能测试和性能调优的思考方法,准确地说,是倾向于找到一些“如何做”的思路。

我会介绍一个性能测试和性能调优的实例。在这个实例中,可能包含了很多技术上的猜测和不准确的理解(我认为性能调优对综合技术能力的要求是很高的),但这不是重点,重点是这个过程中的思路。

我目前从事的工作跟系统底层关系不是很大,现在最常用的都是Web前端、MSQL数据库等这些应用层面的系统。虽然不是直接与系统底层打交道,但是之前的积累无时无刻不在帮助我去深入理解应用开发。比如MSL系统的内存和文件系统的优化,如果对操作系统的虚拟存储和文件系统机制没有深入了解,那么可能只能在配置参数上做一些“猜测”性质的调整、不断地尝试各种参数,或者参考网络上别人提供的配置参数,但不一定适合自己的应用情况。了解虚存如何运作、进程地址空间的分布等,将会对应用的优化甚至是构架设计都会有更高层次的俯视。

——《程序员的自我修养——链接、装载和库》俞甲子、石凡、潘爱民著

在开始介绍这个性能测试和性能调优的实例之前,我还是想强调个观点:软件的性能不应该是在软件开发结束后才开始考虑的问题。在软件的设计和实现阶段,开发人员就应该给予性能以足够的关注。特别是对于持续型的项目来说,在理想的情况下,性能测试和性能调优的工作只是对系统性能的进一步验证和微调。

,让我们开始。先来看看这次性能测试和性能调优的背景。

我们需要对一个大型的企业应用软件进行性能测试和调优。

在这个系统中,包含了大约20000个文件和数百万行代码。功能测试人员发现,有很多页面的响应时间在30s以上,甚至有些页面的响应时间高达数分钟,这距离客户的要求很远。客户要求所有页面的响应时间必须小于5s。不仅如此,客户还描述了这套系统将要面对的业务场景。在公司内部,大约有300名员工使用这套系统,在公司外部,还要响应大约5000名保险代理人的访问。公司每年要处理大约100万张保单。

补充一点,客户的硬件采购计划还没有确定,但是第三方的软件(例如,应用服务器和数据库服务器等)都已经确定了。

在上述的背景下,我们首先确定了性能测试的硬件环境。我们的应用服务器和数据库服务器的硬件配置是一样的:都采用英特尔XEON至强双CPU8核处理器,CPU的主频都是2GHz。两台服务器的内存都是8G,硬盘都使用RAD10方案。由于客户方面的硬件环境尚未确定,所以我们希望通过一定的换算关系推导出系统在不同硬件环境上的性能表现

软件环境是确定的。客户选用 WebSphere作为应用服务器Oracle作为数据库服务器。

回忆一下上一节中提到的四个基本原则:聚焦原则、积累原则自动化原则、目标一致原则。基于这四个基本原则,对于当前的任务,我们应该可以有个简单的思路:首先,通过性能测试来发现系统的性能瓶颈,性能优化将聚焦在一些性能瓶颈上;其次,使用以往的性能测试经验来分析性能报告,同时把性能优化方案提炼成有价值的经验知识;再次,使用自动化工具来进行性能测试和分析;最后,持续地用优化结果提升性能测试目标,用性能测试来验证优化结果。

我们把性能测试和调优的过程分成了两个阶段:第一个阶段聚焦于单用户,第二个阶段聚焦于多用户。按照初步的设想:在单用户下我们会关注耗时的SQL语句、单个事务中的数据库访问次数、明显低效的算法、缓存的使用、计算负载过重的循环块、重复的业务计算等;在多用户下,我们会关注CPU和内存的使用状况、GC的频率、线程竟争、网络流量、数据库负载等。

很明显,经过上面的划分,我们开始有了一些比较具体的设想。这些设想听上去的第一感觉是,尽管头绪繁多,但还算有一定的条理。可是,按照这些设想实际做起来却远远没有听上去那么容易,因为关键的问题还没有解决。例如,如何关注耗时的SQL语句?如何找到针对这些SQL的解决方案?这些经典的“如何”句式是实践者的最爱也是实践者最常感到困惑的地方。

我们选择 Yourkit作为 Java profiler工具。事实上,和大多数的Java profiler工具一样, Yourkit几乎可以提供所有关于性能的数据,其中包括执行SQL语句所占用的CPU时间。不过,和大多数的JavaProfiler工具一样, Yourk提供的性能数据并不准确,这种不准确是由Java profiler工具的实现机制决定的。这些工具大多通过特定的AP(例如,Java5开始提供的 UVMTI-JVM Tool Interface)JM进行通信。

JMT( JVM Tool Interface)Jaa虛拟机所提供的 Inative编程接口,WMP( Java virtual machine Profiler nterface)JMDl( Java Virtual Machine Debug Interface)的更新版本。从这个AP的发展历史轨迹中我们就可以知道,JT提供了可用于 debugfler的接口;同时,Java56,虚拟机接口也增加了监听( Monito-ring)、线程分析( Thread analysis)以及覆盖率分析( Coverage Analysis)等功能。正是由于JMT的强大功能,它是实现Java调试器以及其他Jva运行态测试与分析工具的基础。

JMT使用了所谓的“字节码检测”( bytecode instrumentation)技术,这意味着 Java profiler工具在收集分析数据时会修改应用程序的字节码,并在特定的位置插入一些字节码指令。这显然会影响被分析应用程序的执行效率。有时候,这种影响会干扰分析人员的判断当然,如果你追求比较接近真实的性能数据,就可以关闭 Recordingobject al- ocation CPU tracing而使用 CPU sampling功能,但是影响仍然存在。

Yourki作为数据库服务器的客户端,其实无法真正地给出SQL语的执行时间,这里面也许有网络的因素。在大多数的情况下,我们不关注客户端或者服务器端对SQL语句执行时间统计的差异,尽管不在一个数量级上,但绝对差值并不大。但是,有些时候我们仍然需要对SQL语句执行效率的细微差异进行关注。例如,SQL语句执行的次数非常多。

一个好的建议是,直接使用 Oracle的统计表。我们可以清除SQL语句的缓冲池,在执行个事务后,观察缓冲池中SQL语句的执行状况,例如,执行次数、平均执行时间等。我们选取了那些 Top SG对它们的执行计划进行了分析。

通过参阅相关资料和多次尝试,我们得到了一些经验知识:

提升SQL语句的执行效率本质上就是降低磁盘的访问次数,通常来说,可以通过建立索引和避免对大数据量的表进行全表扫描来实现;

索引稠密度并不能完全说明建立索引的合理性,还需要考虑数据分布,因此,索引应该尽量建在取值比较均匀的字段上。

这些经验知识帮助我们明确了SQL语句的优化方向,同时也取得了很好的效果。我们甚至利用oace的特性来建立或屏蔽动态索引以及干涉SαL语句的执行路径。总之,目标只有一个——降低磁盘的访问次数。

在对缓冲池的观察中,我们还发现很多相似的语句大量出现,这显然是没有使用绑定变量的缘故。在单用户下,这种做法导致的性能问题可能还不明显,但是在多用户并发的情况下(换句话说,在数据库访问极其频繁的情况下),会产生性能问题。这是因为,缓冲池是有一定容量限制的,当不断有新的SQL语句进入时,原先的SQL语句会被移除,这导致了sαL语句的硬解析。硬解析会使优化器重新创建解析树和生成执行计划,而这两个动作都是代价昂贵的,会影响SQL语句的执行性能。

至此,对于SQL语句执行性能的问题,我们已经有了比较明确的解决方案。事实上,在以上提到的性能问题中,哪一点是软件开发设计和实现中无法避免的呢?没有。

我们把视线转向单个事务中的数据库访问次数。令人震惊的是在一个事务中,竟然有几千次的数据库访问。每一次数据库访问,都需要客户端和服务器端的交互。这种交互会在网络上传递“相当可观”的字节数,更不要说CPU和内存上的消耗。

我们对这个现象进行了分析,很快发现以下的问题。

首先,系统中存在JDBC Hibernate混用的情况。这导致了Hibernate大量的fus动作,fush会产生数据库的操作。这种做法让人厌恶。

这是软件设计和实现中无法避兔的吗?不。

其次,很多方法使用D作为参数,每一次调用都要通过D来构建数据对象,这导致了大量的数据库访问。

这是软件设计和实现中无法避免的吗?不。

最后,在循环中通过JDBC来访问数据库,如果循环次数很多,数据库访问次数会让人发疯。

这是软件设计和实现中无法避免的吗?不。

我们尝试使用减少ush次数、传递对象、循环体外准备数据等方式来解决上面的问题。尽管取得了一定的效果,但感觉很差。如此大规模的程序,其结构仿佛来自本能,而不是来自良好的规划。

接下来,我们开始在 Yourkit的分析报告中寻找明显低效的算法。这看上去很容易,因为除非我们设置了过滤,否则所有方法的执行时间都清楚地显示在报告清单上面。但是,这实际上又不大容易,因为Java profile工具对于应用程序的性能是有干扰的。在这个阶段,我们必须找到那些值得怀疑的方法,阅读它的源代码,并结合经验知识来做出判断。要解决这类问题没有什么捷径。

软件开发设计和实现无法解决低效的算法问题吗?不。

我们发现,有些数据在计算的过程中不会或者很少发生变化。这些数据或者来自数据库,或者来自文件,或者来自计算。这对吗?不对。它们应该来自缓存。

这是软件设计和实现无法解决的问题吗?不。

我们发现,在某些循环体中,包含了大量的计算。这就像一颗定时炸弹。循环次数少的时候,看上去风平浪静;一旦数据规模发生变化,这个循环块就变成了吞噬CPU资源的怪兽。

循环体中要做尽可能少的事!这是软件设计和实现无法解决的问题吗?不。

我们发现,某些事务中包含了重复的业务计算。当我们询问重复的原因时,甚至没有人可以完整地介绍这个业务故事。我们找不到设计文档,也没有人能说清楚这个故事,可系统竟然能够正常运行,这太奇怪了!但是,话说回来,这太常见了。为什么可以做到这样?我可以告诉你答案,这是大量低效的功能测试和不断地尝试拼凑起来的。在这样的软件开发组织中,软件测试就像是最后一根救命稻草。

难道清晰的业务逻辑是软件设计和实现无法解决的问题吗?不。

我们在单用户下设置的目标已经基本达到了,但是实际上还没有完。我们又发现了一些新的问题。例如,有些方法会接受一些输入参数,尽管这个方法本身可能在某些逻辑路径下会快速地返回,但是输入参数需要进行计算,例如Log4中的 debug方法。因为我们无法预知哪些计算会占用多大比例的CPU时间,所以在计算输入参数之前进行些逻辑判断是必要的。

在线协同需求文档

1、石墨文档

石墨文档是一款轻快好用的在线协作文档,支持多人同时在线编辑同一个文档。运营人员可以用它来多人协作撰写文案、一起制定运营规划和开展头脑风暴等。产品经理可以直接在石墨文档里撰写产品需求文档,整理用户的产品使用反馈,更新bug修复与新功能实现情况等。此外,产品经理还可以邀请软件开发团队成员在同一个文档里共同撰写团队工作日报和周报,也可以直接在文档里分配工作任务并键更新任务完成情况。