性能问题的定位分析(五)——大话性能测试(26)

发表于:2022-7-26 09:44

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:胡通    来源:51Testing软件测试网原创

  4.性能调优建议
  性能调优是一个非常大的议题,更多的是由开发人员来进行。测试人员可以了解一些通用的调优方法,并根据性能分析过程中发现的问题,给出一些建议。当然,随着性能测试经验的积累,测试人员也会知道很多开发人员不知道的调优方法。
  (1)设计优化。
  设计优化处于所有调优手段的上层,它往往需要在软件开发之前进行。在软件开发之初,架构师就应该评估软件可能存在的各种潜在问题,并给出合理的设计方案。由于软件设计和架构对软件整体质量有决定性的影响,因此设计优化对软件性能的影响也是最大的。
  从某种程度上说,设计优化直接决定了软件的整体品质。如果在设计层考虑不周,留下太多隐患,那么这些“质”的问题,也许无法通过代码层的优化进行弥补。因此,开发人员必须在软件设计之初,认真仔细考虑软件系统的性能问题。
  进行设计优化时,设计人员必须熟悉常用的软件设计方法、设计模式、基本性能组件和常用优化思想,并将这些有机地集成在软件系统中。
  注意,一个良好的软件设计可以规避很多潜在的性能问题。因此,尽可能多花些时间在系统设计上是创建高性能程序的关键。
  (2)算法优化。
  算法非常重要,好的算法会使系统有更好的性能。例如,分而治之和预处理的思路。某程序为了生成月报表,每次都需要计算很长的时间,有时候需要花费将近一整天的时间。于是我们找到了一种方法将这个算法改成增量式的,也就是说我们每天都把当天的数据计算好之后和前一天的报表数据合并,这样就大大节省了计算时间。调整之后,每天的数据计算时间需要大约20min,但是如果我一次性计算整个月的数据,系统需要10h以上(SQL语句在大数据量面前性能成级数下降)。这种分而治之的思路对大数据应用的性能有很大的帮助。
  (3)代码优化。
  代码优化是在软件开发过程中或者在软件开发完成后的软件维护过程中进行的,对程序代码的改进和优化。代码优化涉及诸多编码技巧,需要开发人员熟悉相关语言的API,并在合适的场景中正确使用相关API或类库。同时,对算法、数据结构的灵活使用,也是实现代码优化的必备技能。
  虽然代码优化是从微观上对性能进行调整,但是一个“好”的实现和一个“坏”的实现对系统影响的差异也是非常大的。例如,同样作为List的实现,LinkedList和ArrayList在随机访问上的性能可以相差几个数量级;同样是文件读写的实现,使用Stream方式与JavaNIO方式,其性能可能又会相差一个数量级。
  因此,虽然与设计优化相比,这里将代码优化称为在微观层面上的优化,但是它却是对系统性能产生最直接影响的调优方法。
  (4)JVM优化。
  由于Java程序总是运行在JVM之上,因此对JVM进行优化也能在一定程度上提升Java程序的性能。JVM优俪常可以在软件开发后期进行,如在软件开发完成时或者在软件开发的某一里程碑阶段。
  作为Java程序的运行平台,JVM的各项参数将会直接影响Java程序的性能。例如,JVM的堆大小和GC策略等。
  要进行JVM层面的优化,需要开发人员对JVM的运行原理和基本内存结构有一定的了解,如堆的结构和GC的种类等。进而,依据应用程序的特点,设置合理的JVM启动参数。
  (5)参数优化。
  中间件参数。互联网产品的系统架构总是会涉及各种中间件,常见的有Nginx、ZooKeeper、Tomcat、Redis、RabbitMQ等。这些组件和性能息息相关的参数的默认值有时候是不满足业务要求的,因此需要优化。
  系统参数。Linux系统本身的一些连接数、网络、读写内存分配大小的参数。
  (6)数据库优化。
  对绝大部分应用系统而言,数据库是必不可少的一部分。Java程序可以使用JDBC的方式连接数据库。对数据库的优化可以分为3个部分:在应用层对SQL语句进行优化、对数据库进行优化和对数据库软件进行优化。
  数据库优化是一个很大的话题,下面是作者总结的一些经验。
  数据库引擎调优。数据库的锁的方式非常的重要,因为在并发情况下,锁是非常影响性能的。它包括各种隔离级别,行锁、表锁、页锁、读写锁、事务锁,以及各种写优先和读优先机制。要想达到最高的性能最好是不要锁,所以,分库分表、冗余数据、减少一致性事务处理,可以有效地提高性能。NoSQL就是牺牲了一致性事务处理,并冗余数据,从而达到了分布式和高性能。
  数据库的存储机制。不但要搞清楚各种类型字段是怎么存储的,更重要的是了解数据库的数据存储方式,即它是怎么分区、怎么管理的。了解清楚这个机制可以减轻很多的10负载。例如,在MySQL下使用showengines,可以看到各种存储引擎的支持。不同的存储引擎有不同的侧重点,针对不同的业务或数据库设计会有不同的性能。
  数据库的分布式策略。最简单的就是复制或镜像,需要了解分布式的一致性算法,或是主主同步、主从同步。通过了解这种技术的机理可以做到数据库级别的水平扩展。
  SQL语句优化。关于SQL语句的优化,首先是要使用工具,例如MySQL的SQLQueryAnalyzer可以用于査看应用中的SQL的性能问题,还可以使用explain来査看SQL语句最终的ExecutionPlan是什么样的。
  还有一点很重要,数据库的各种操作需要大量的内存,所以服务器的内存要够,尤其应对那些多表査询的SQL语句,是相当耗内存的。
  全表检索。例如SELECT*FROMuserWHERElastname="xxxx",这样的SQL语句基本上是全表查找,线性复杂度0(")越高、记录数越多,性能也越差(如査找100条记录要50ms,而査找一百万条记录需要5mm)o对于这种情况,我们可以有两种方法来提高性能:一种方法是分表,把记录数降下来;另一种方法是建索引(为lastname建索引)。索引就像是键值对的数据结构一样,键就是WHERE后面的字段,值就是物理行号,对索引的搜索复杂度基本上是O(log(?))用B-Tree实现索引(如査找100条记录要50ms,而査找一百万条记录需要100ms)
  -索引。对于索引字段,最好不要在字段上做计算、类型转换、函数调用、空值判断和字段连接等操作,这些操作都会破坏索引原本的性能。当然,索引一般都出现在WHERE或是ORDERBY子句中,所以在WHERE和ORDERBY子句中的字段上最好不要进行计算操作,或是加上NOT之类的关键字,或是使用函数。
  .多表醐。对关系型繩库做的最多的操作就是多表SW,多表SW主要有3个獭字,EXISTS、IN和JOIN。基本上,现代的数据库引擎对SQL语句优化得都挺好的,这3个关键字的运用在结果上有些不同,但在性能上基本都差不多。有人说,EXISTS的性能要好于IN,IN的性能要好于JOIN。作者个人觉得,这个还要看数据、模式和SQL语句的复杂度的情况。对一般的简单的情况来说性能都差不多,所以千万不要使用过多的嵌套,千万不要让SQL语句太复杂。宁可使用几个简单的SQL语句也不要使用一个巨大无比的嵌套N级的SQL语句。还有人说,如果两个表的数据量差不多,EXISTS的性能可能会高于IN,IN可能会高于JOINo以作者的经验来看,如果这两个表一大一小,那么子查询中,EXISTS用于大表,IN则用于小表。
  JOIN操作。有人说,JOIN表的顺序会影响性能。实际上只要JOIN的结果集是一样的,性能和JOIN表的顺序无关,因为后台的数据库引擎会帮我们优化的。JOIN有3种实现算法,嵌套循环、Hash式的JOIN和排序归并(MySQL只支持第一种)。嵌套循环与我们常见的
  多重嵌套循环基本一样。注意,前面的索引部分说过,数据库的索引査找算法用的是B-Treeo这是O(log(n))的算法,所以整个算法复杂度应该是O(log(n))*0(logO?))。Hash式的JOIN,主要解决嵌套循环的O(log(?))复杂度的问题,使用一个临时的hash表来标记。排序归并,意思是两个表按照査询字段排好序,然后再合并。当然,索引字段一般是排好序的。
  部分结果集。我们知道MySQL里的LIMIT关键字、Oracle里的ROWNUM、SQLServer里的TOP都是在限制前几条的返回结果。这给了我们很多对数据库引擎调优的空间。一般来说,返回前n条的记录数据需要我们使用ORDERBY,注意在这里我们需要为ORDERBY的字段建立索引。有了索引后,会让我们的SELECT语句的性能不会被记录数所影响。
  -字符串。如前文所述,字符串操作对性能的影响很大,所以,能用数字的情况下就用数字,例如用时间和工号等。
  -全文检索。千万不要用LIKE之类的关键字来做全文检索,如果要做全文检索,可以尝试使用Sphinx。
  其他一些建议。
  不要用SELECT*,而是要明确指出各个字段;如果有多个表,一定要在字段名前加上表名,不要让数据库引擎去算。
  -不要用HAVING,因为其要遍历所有的记录,性能差得不能再差。
  尽可能地使用UNIONALL取代UNIONo
  索引过多,INSERT和DELETE就会变慢。而如果UPDATE多数索引,速度也会变慢;但是如果只UPDATE一个索引,则只会影响一个索引表。
  以上是性能问题的定位分析的所有内容,知识点比较多,大家需要好好消化吸收,并在实战中多应用实践。另外在此引入一个分析问题的方法论,套用5W2H方法,可以提出性能分析的几个问题。
  Who:发生了什么现象?
  What:现象是什么样的?
  When:什么时候发生?
  Why:为什么会发生?
  Where:哪个地方发生的问题?
  Howmuch:耗费了多少资源?
  Howtodo:怎么解决问题?
  提示
  面对流量洪峰的策略如下。
  (1)服务降级。服务降级是指在特定情况下,例如“双十一”“双十二”期间,当流量超过系统服务能力时,跳过特定的处理流程。比如在一个买家下单后,我们可能需要进行风险评估、数据校验等一系列流程,当发生服务降级时,就跳过了数据校验逻辑,以避免用户长时间等待,降低对下游链路的冲击,保证服务的稳定性。服务降级是面对流量洪峰保证用户体验和预防系统崩溃的有效手段。
  (2)服务限流。服务限流是指根据服务的处理能力提前预估一个阈值,当流量大于该阈值时放弃处理直接返回错误。服务限流是应对流量达到峰值时,系统进行自我保护的重要措施。例如“双十一”零点下单峰值、余额宝九点抢购峰值以及活动结束商品信息编辑峰值,都需要进行相应的限流来保护系统。
  (3)故障容灾。单机系统的容灾能力几乎为零,一旦艮务崩溃就马上变成不可用。分布式系统通过异地多活,可以不间断地提供服务。同时,借助于Nginx、Apache进行负载均衡可以进一步提高可用性。
  实际上,即便进行了负载均衡和服务分布式部署,系统仍然面临容灾问题。现在的大型服务,例如淘宝、天猫、微信、京东都进行了异地多活的部署。异地多活部署的主要目的是,通过多机房提供服务来降低单机房故障带来的影响,提高容灾能力。
  1.2.10性能测试的报告总结
  经过多轮重复测试和优化,在满足性能需求后,需要编写性能测试报告,及时做好知识沉淀和总结,吸取经验,避免以后二次踩坑。
  性能测试报告中至少要包括系统的测试范围、测试目标、测试方法、场景设计说明、测试数据准备、测试计划、测试结果,以及优化过程和建议等。
  性能测试报告中,我们需要重点描述多轮测试和优化所做的改动和调整,并把相关的变动及时同步给运维人员和开发人员,避免上线的时候没做调整,出现线上故障,这也就是之前说的闭环的重要性。
查看《大话性能测试》全部连载章节
版权声明:51Testing软件测试网获得作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号