vlambda博客
学习文章列表

性能测试在货拉拉小橙点中的实践与应用

本文将介绍性能测试在货拉拉日常项目中的实践与应用,文章以小橙点服务(地图组应用-推荐装卸货点)为例,整体描述了性能设计方案设计思路,测试过程,影响因素,及性能定位及优化的方法和手段,希望能和大家一起为货拉位高并发应用带来更高性能和更稳定的解决方案,与大家,与项目,与公司共同进步成长。

1. 背景

2. 性能测试的主要流程

性能测试通常流程如下图所示,实际在比较的紧张的项目周期和项目规模的不同可能会存在较小的差异,但一般都需要包含方案、环境、数据、脚本、执行、报告这几个环节。

3. 性能测试方案

性能测试方案是对性能需求进行进一步分析后,在测试策略、测试周期、测试方法、数据准备、环境准备各项内容的安排,为进一步的性能测试提供测试准备,每个项目的性能测试的方案都随具体应用的架构和实现逻辑会有所区别和不同,在本项目经初步调研服务使用JAVA编写,查询数据库使用ES,针对这样一个比较典型的三层框架,在设计方案时需要考虑以下因素对性能结果的影响。

3.1 缓存,测试时要不要命中

缓存在IT系统中几乎无处不在,从CPU的1,2,3级cache,各种传统数据库都有对热点数据的缓存,到现今几乎无处不在Redis这种应用级的缓存数据库。服务在命中缓存时和没有命中缓存时通常会有一个数量级以上的性能差别,那在性能测试过程要不要使请求命中缓存呢?性能脚本要不要命中缓存要以具体场景具体分析,比如一个司机的信息的Redis缓存,以司机Id做为Key,像这种司机长时间保持在线且经常查询服务的,可以采用全命中缓存的机制;但在本项目中,是要以司机和用户的GPS经纬度来查询相关推荐点的,而经纬度通常需要考虑精度而保留6位以上的小数,在真实场景中即使同一用户也不太可能在两次使用的时候有相同的经纬度,所以在脚本设计发送服务请求的时候就要使用动态的经纬度,避免命中数据库的热点数据形成的缓存数据。

3.2 服务器配置影响有多大

通常的性能测试工作是在性能环境中进行的,性能环境有比较独立的服务器且应用服务器配置与生产环境相同的均为8C16G的硬件配置,差距主要集中在数据库方面,本例中ES服务器在性能环境同样3台8C16G配置,而生产上ES的配置3台16C64G配置,ES也是吃内存的大户,所以数据库的配置差距对性能还是影响较大的。一般的服务的数据库的查询性能比应用的性能通常能高上一个数量级,比如数据库查询瓶颈性能在数万QPS,而大部分应用的性能多是在数千QPS以内,所以通常情况下是先到达了应用的性能瓶颈,此时在性能环境测试是不影响的测试的整体结果的;而如果是先达到了数据库的瓶颈,就需要考虑数据库扩容或是在生产环境中进行测试了。

3.3 基础数据的影响

基础数据量是指在查询时数据库表中有多少的已存在的数据,也叫存量数据。基础数据的多少不但会直接影响到性能测试的准确性,而且超大量的基础数据甚至会改变整个数据库的架构,如分库分表等策略,甚至是催生出新的行业岗位,数据仓库、大数据等。所以在做性能测试时,一定要保证数据库中有与生产环境较接近的基础数据,最好是数据能保持相同或接近,最差也不能有数据量级上的差距。经调研,本次测试中短期内生产使用的数据在3800万条,24G大小的数据量,这些数据需要提前导入到数据库。

4.测试执行

性能测试执行包括脚本准备、测试数据、执行压测脚本、回归测试、问题定位与优化,是性能测试过程中最主要的活动,在避免性能测试方案的主要影响因素并对对应的环境和数据进行部署后,就来到了这个主要环节。

4.1 性能环境性能陨落

经在性能环境初步测试,性能结果并不好,主要接口服务的QPS为195,这是200人同时访问不停刷新都要凉凉的节奏啊。与开发初步沟通后,怀疑是ES数据配置相差太大引起的,因为配置差距的确存在,于是准备生产环境相关服务器,转战生产环境进行测试。

初次性能环境测试结果

性能测试在货拉拉小橙点中的实践与应用

4.2 生产环境折戟沉沙

在生产环境同样准备数据、应用、脚本等基础条件后。上测试,一波操作猛如虎,性能不到195,生产环境性能与性能环境表现基本一致,甚至还没性能环境好。有问题,绝对有性能问题,下一步就是准备定位及优化复测的工作。

生产环境的最初测试结果

性能测试在货拉拉小橙点中的实践与应用

4.3 重回性能环境定位问题

为什么要回到性能环境来定位问题,主要是服务器权限问题,定位问题需要有比较频繁的使用监控工具和修复发布代码的操作,而生产环境则需要遵守相关的安全规定及发布规范。所以为了测试定位的问题方便性,重新回到性能环境进行了部署。

5.性能问题定位

不同的性能问题有不同的定位重点和方法,通常在初步的判断服务器资源、性能QPS和响应时间后大概可以缩小定位的方向,从而缩小问题的范围。

5.1 QPS较低性能类问题的定位方法

QPS较低的含义就是单位时间内服务器处理的请求量较少,这是一个和处理请求量和时间都有关系的指标,一般的定位思路就是通过监控或是日志追踪到响应较慢的处理方法,从而判断问题原因。JAVA应用有比较成熟的方法级追踪定位工具,可以较快速的定位代码方法级的性能问题,比如本例使用的arthas。使用arthas命令trace cn.xxx.xxx.class method进行追踪定位,效果类似下图,并对响应较慢的请求再次追踪定位,很快定位到是cn.huolala.bizp.map.controller.UserRecController类中使用的validate方法是整个链路的最慢的,而validate是使用的比较有名的Validator来实现。

性能测试在货拉拉小橙点中的实践与应用

使用arthas的monitor方法进行方法的监控发现,每次validate方法耗时接近300ms。

性能测试在货拉拉小橙点中的实践与应用

到此基本确定首个性能问题的原因,与开发沟通后此方法主要作用是校验请求的参数是否符合预期的入参,功能比较独立,直接注释掉对应的代码块进行验证。再次启动脚本压测,QPS来到了580,处理能力有大幅提升,确定参数校验方法有性能问题,首个优化点带了3倍多的性能提升。

5.2 常规性能问题的定位方法

在初步屏蔽了性能问题代码后,性能有所提升,处理能力来到QPS600左右,仍然使用时间损耗长短来定位问题,发生性能耗时在ES查数据的方法上,虽然应用此时600QPS是初步满足生产当前业务量的需求,但性能瓶颈一旦是数据库瓶颈,则意味着生产发生问题不能有效的快速扩容,最好是能进一步优化。

性能测试在货拉拉小橙点中的实践与应用

org.elasticsearch.client.RestHighLevelClient:search()方法仅仅是去ES数据库拿数据的方法,已经毫无优化空间了。此时,需要把目光上升到全局,思考为什么查询会慢,应用无优化空间,ES的参数、分片、索引、配置均会影响到查询性能,在确定索引和过滤条件没有大问题后,查看ES的服务器资源CPU已经超过80%,是ES的资源问题,已经没有优化空间了吗?其中一条监控数据略有异常,那就是ES的query数量,经查在压测期间,ES的查询的QPS在17000左右。这就比较有意思了,请求580QPS,而到了ES这里了查询命令的QPS高达17000(见下图),每一个请求对应了30次的数据库查询,这是一条重要的线索。

性能测试在货拉拉小橙点中的实践与应用

在排查不是程序放大了数据库的请求后,目光来了数据库自身,经DBA沟通排查,DBA反馈ES对应的服务索引的分片是30个,答案呼之欲出。因为在查全量的数据时,每个分片均会产生一条查询请求,所以有了30倍的比例,那么30个分片合理吗?如果不涉及缓存,则最小查询延迟将取决于数据、查询类型以及分片的大小。查询大量小的分片将使每个分片的处理速度更快,但是需要按顺序排队和处理更多的任务,它不一定比查询较少数量的较大分片更快,如何确定分片的大小,需要根据业务量的大小和增速来合理规划。DBA根据以当前业务量规模及数据量大小,预估3个分片即可有较高的性能,于是重建索引与分片数量,经测试请求的性能骤然提高到了3230QPS,又是5倍的性能提升。

在生产实际调整部署时考虑到后续业务的增长和数据的增长,将索引分片的数量定格在了5个分片上,经测试请求的性能在2200QPS左右(见下图),充分印证了分片的大小和多少对性能的影响,在完成稳定性测试后项目正常上线。同时也带给我们一些思考,分表及分片能处理数据量带来的一些性能问题,同时也可能潜在会引发查询量的性能问题,需要在其中均衡要害关系并且谨慎处理查询代码避免全量查询。

性能测试在货拉拉小橙点中的实践与应用

6.代码的优化方案

根据问题定位及回归测试,已经确定的对应的问题,但当时修复措施是屏蔽参数校验的代码,显然这在功能上会带来一定的缺失和隐患,到底是什么原因导致这个Validator类的使用有问题了呢?

在这个项目因为上线和时间因素比较赶就没有追根问底,一直到另一个项目使用同样的配方、相同的味道再来一遍,于是想进一步查找Validator出现性能问题的原因。

6.1 重现问题

把当时出问题的代码单独拿出来初步验证,其主要实现方法如下:

private static ResultModel validate(Object object) { //获得验证器 Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); //执行验证 Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object); //如果有验证信息,则将第一个取出来包装成异常返回 for (ConstraintViolation<Object> constraintViolation : constraintViolations) { return ResultModel.error(constraintViolation.getMessage()); } return null;}


写一个简单的测试类让其验证100次对象,小计耗时2772ms,平均单次耗时28ms,因为这个是无并发的情况下的数据,算是很差的性能表现了。

UserRecParam user = new UserRecParam();long start = System.currentTimeMillis();for (int i = 0; i < 100; i++) { validate(user);}long end = System.currentTimeMillis();System.out.println("cost time :" + (end - start));


6.2 查看源码

在网上查对应的实现方法,大部分都是这样的写的感觉也没啥毛病,有人反应有性能问题,但也没指明原因和优化方案,于是扒拉扒拉源码吧,从获取对象的工厂方法看起。

public static ValidatorFactory buildDefaultValidatorFactory() { return byDefaultProvider().configure().buildValidatorFactory();}

查看byDefaultProvider发现,这不是一个单例工厂,每次获取都会有一个生成新的对象,但生成新的对象也不算大毛病,也许是需求如此,只能算是一个怀疑点。

public static GenericBootstrap byDefaultProvider() { return new Validation.GenericBootstrapImpl();}


接着过配置、过一下感觉没问题,接着看buildValidatorFactory,其中有一段这样的代码

finally { // close all input streams opened by this configuration for ( InputStream in : configurationStreams ) { try { in.close(); } catch (IOException io) { LOG.unableToCloseInputStream(); } }}


一般用到InputStream类都涉及到文件、输入输出、序列化等比较重的操作,结合每次生成新的对象的工厂方法,操作又重,生成次数又多,有那个味了。

6.3 优化验证

知道问题的疑似原因,我们就好做出对应的优化和验证,最简单的方法把获取Validator验证器的方法从静态方法移出去,使其成为一个静态变量,移出后使用相同的测试程序验证100次,得到278ms的结果,与原来的方法的性能相比提升了一个数量级,到这里项目的性能原因的已经基本达到归零要求,至于优化方案不管是自己实现单例对象或在Springboot2.0后实现的Validator的注解@Validated来实现参数验证,还是实现更好的优化检验方法,都可以解决这个问题。

7.总结

推荐装卸货点服务在通过性能测试与开发及DBA的配合优化,性能在最初的190QPS左右,提升到2200QPS左右,性能得到极大的提升,并且保证了一定业务的增长下的冗余性能,项目上线后对打黑车、提升NPS等也起到明显的作用,同时成功保证了业务对性能的需求。性能测试是一个要求对整个系统知识面都需要有一定程度了解的岗位,从压测工具、操作系统、应用程序的开发、到各种的中间件、数据库各个因素都可能影响到性能测试的结果和准确性,通过知识的积累才能更好的完成性能测试工作,同时在文中如果有什么错误和可优化的地方,也希望大家及时指正,在后续的工作也希望各个领域内的专业大佬多多沟通交流,为公司的应用的性能提升做到添砖加瓦不添堵,谢谢大家。

8.后记

本文所示例项目中所提到的两个性能问题,非单例的重对象和请求到数据库命令放大,均是比较常见的性能问题,此类相同的性能问题在其它项目的性能测试中也是频繁出现,写此后记是希望看到本文的开发大佬可以检查一下性能测试没有覆盖到的项目代码,并做出对应的优化,不留性能隐患。

作者介绍:魏占飞(eric.wei),资深测试工程师