之前写了两篇文章分享自己对几种性能测试框架的测试:​​性能测试框架对比初探​​​、​​性能框架哪家强—JMeter、K6、locust、FunTester横向对比​​。

上次的测试中,我在局域网起了一个基于​​FunTester moco server框架架构图​​​的服务,服务单机​​QPS​​在15k左右到达瓶颈,但是初步判断是局域网带宽导致的,由于时间原因我并没有在深入排查原因。刚好一个朋友想知道​​Gatling​​性能测试框架在实际测试中跟其他框架的比较结果,我就趁着周末时间搞了一个本地的​​moco服务​​来测试「K6」「Gatling」「FunTester」三个测试框架在「10万QPS」级别测试中的表现。

准备工作

本机硬件​​2.6 GHz 六核Intel Core i7​​,CPU统计数据来自​活动监视器​​,​​100%​​代表消耗了一个CPU线程,理论上全部CPU资源当做​​1200%​​,内存数据也来自​​活动监视器​​。首先我利用​​FunTester moco server框架架构图​​测试框架在局域网环境起了一个测试服务,只有一个「兜底」接口。​​Groovy​​脚本如下:

import com.mocofun.moco.MocoServer
class TestDemo extends MocoServer{
static void main(String[] args) { def log = getServerNoLog(12345) log.response("hello funtester!!!") def run = run(log) waitForKey("fan") run.stop() }}

由于放在了本机,所以也就基本不用考虑网络带宽问题,经过本人自测,「QPS」实测数据最高「12万」,所以本次测试结果基本都在「10万QPS」这个级别上。而且单机线程数会从更低的「1并发」开始,实测当达到「10并发」时,本机CPU已经跑满了(被测服务消耗大概「25%CPU」)。

由于​​Gatling​​使用的脚本语言​​Scala​​和「FunTester」测试框架使用的脚本语言​​Groovy​​都是基于JVM的语言,所以我均采用默认配置进行测试,不再进行修改​​JVM​​参数的测试,主要原因是不会​​Scala​​修改​​JVM​​参数。

脚本准备

K6

脚本内容如旧文:​​性能框架哪家强—JMeter、K6、locust、FunTester横向对比​​。

FunTester

本机​​Java SDK​​版本同上,​​Groovy SDK​​版本:「Groovy Version: 3.0.8 JVM」。​​Java​​堆内存设置「1G」,其他参数默认。

脚本内容如旧文:​​性能框架哪家强—JMeter、K6、locust、FunTester横向对比​​。

Gatling

脚本内容改编自自带模板,内容如下:

package computerdatabase
import scala.concurrent.duration._
import io.gatling.core.Predef._import io.gatling.http.Predef._
class FunTester extends Simulation {
val httpProtocol = http .baseUrl("http://localhost:12345/m")val scn = scenario("FunTester").repeat(120000){ exec(http("FunTester").get("/m")) }
setUp(scn.inject(atOnceUsers(10)).protocols(httpProtocol))}

实战开始

如我之前所说,由于QPS太高,导致很低线程即跑满本机CPU,所以继续增加并发数没有多大意义了。所以本地都是在较低线程数情况测得。

这里解释一下线程数和并发数,在部分框架中,有些框架称为用户数,有些叫做线程数和并发数。本期都成为并发数,与旧文并发数一致。

由于各个框架使用的平均响应时间(​​RT​​)都是​​ms​​单位计算的,所以我在平均影响时间小于​​1ms​​的时候把平均响应时间记作​​1ms​​。

1并发

测试结果:


框架

CPU

内存

QPS

RT

K6

136.75

97

10543

1

Gatling

88.01

344

19506

1

FunTester

56.12

539

18859

1

​Gatling​​测试框架在计算测试成果,生成测试报告的时候使用​​CPU​​会更高,这一点让我有点意外。这里​​K6​​测试的​​QPS​​偏低,有点小意外。「FunTester」这里消耗内存比较多,还能接受。

5并发

测试结果:


框架

CPU

内存

QPS

RT

K6

449.15

139.5

37219

1

Gatling

341.19

350.5

63624

1

FunTester

243.19

945.0

71930

1

​Gatling​​计算测试结果生成测试报告时候消耗​​CPU​​跟单线程一致,在​​100%​​上下,但是耗时明显增长了很多。到这里,「FunTester」的表现还是可以的,我总结了一下内存占用比较高的原因,应该是我测试过程中把测试数据存在内存里面了。这里「K6」测试框架测出来的​​QPS​​大概是其他两个框架的一半。

10并发

测试结果:


框架

CPU

内存

QPS

RT

K6

702.05

299.9

61087

1

Gatling

524.70

350.2

94542

1

FunTester

460.13

1170

91360

1

​Gatling​​输出报告的时间有点长,3百万数据量消耗的时间,有点不太能接受了。「K6」这时候消耗​​CPU​​有点多了。但是​​QPS​​依然有点低。「FunTester」占用内存已经超过1G了。这个时候本机​​CPU​​使用率已经超过了*90%*了。后面增加​​20​​并发再测一下看看​​CPU​​不足时候测试结果。

20并发

测试结果:


框架

CPU

内存

QPS

RT

K6

718.74

370.0

75980

1

Gatling

585.97

350.0

113355

1

FunTester

528.03

1770

104375

1

测试完成,这轮测试「K6」表现有点逊色,应该​​CPU​​已经瓶颈了,导致测试QPS相比偏低。同属​​JVM​​语言,​​Gatling​​和​​FunTester​​基本数据保持在一致,其中​​FunTester​​消耗比较多,这一点目前来讲,我认为影响不是很大,暂不优化了。

PS:私下测试了更高并发的,结果跟20并发的差不多。

总结

这次测试有一个现象,​​Gatling​​框架测试​​QPS​​要比​​FunTester​​高一点,这里我总结了一下原因:


  1. FunTester做了更多适配,体现在标记对象
  2. FunTester同步执行了更多判断,体现在终止条件上
  3. FunTester同步存储了测试数据

这里我观察到的现象是​​FunTester​​框架使用了更多的内存,​​Gatling​​创建了更多的线程(此处我怀疑是异步处理一些事情),​​Gatling​​没有在可能的业务层面留下兼容功能(如标记对象,错误日志个性化记录)。

基于此,我列了几条「FunTester」优化方向:


  1. 将非必要的处理改成异步
  2. 尝试更换测试元数据存储方式
  3. 逐步丢弃业务相关兼容代码(已完成)

首先看一下核心执行代码:

@Override    public void run() {        try {            before();            long ss = Time.getTimeStamp();            int times = 0;            long et = ss;            while (true) {                try {                    executeNum++;                    long s = Time.getTimeStamp();                    doing();                    et = Time.getTimeStamp();                    int diff = (int) (et - s);                    costs.add(diff);                } catch (Exception e) {                    logger.warn("执行任务失败!", e);                    errorNum++;                } finally {                    if ((isTimesMode ? executeNum >= limit : (et - ss) >= limit) || ThreadBase.needAbort() || status())                        break;                }            }            long ee = Time.getTimeStamp();            if ((ee - ss) / 1000 > RUNUP_TIME + 3)                logger.info("线程:{},执行次数:{},错误次数: {},总耗时:{} s", threadName, executeNum, errorNum, (ee - ss) / 1000.0);            Concurrent.allTimes.addAll(costs);            Concurrent.requestMark.addAll(marks);        } catch (Exception e) {            logger.warn("执行任务失败!", e);        } finally {            after();        }    }


「Have Fun ~ FunTester !」




「FunTester」,​​腾讯云年度作者​​​、​​Boss直聘签约作者​​​,​​GDevOps官方合作媒体​​,非著名测试开发,欢迎关注。