• 10万QPS,K6、Gatling和FunTester终极对决!


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

    上次的测试中,我在局域网起了一个基于FunTester moco server框架架构图的服务,服务单机QPS在15k左右到达瓶颈,但是初步判断是局域网带宽导致的,由于时间原因我并没有在深入排查原因。刚好一个朋友想知道Gatling性能测试框架在实际测试中跟其他框架的比较结果,我就趁着周末时间搞了一个本地的moco服务来测试K6GatlingFunTester三个测试框架在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使用的脚本语言ScalaFunTester测试框架使用的脚本语言Groovy都是基于JVM的语言,所以我均采用默认配置进行测试,不再进行修改JVM参数的测试,主要原因是不会Scala修改JVM参数。

    脚本准备

    K6

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

    FunTester

    本机Java SDK版本同上,Groovy SDK版本:Groovy Version: 3.0.8 JVMJava堆内存设置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语言,GatlingFunTester基本数据保持在一致,其中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官方合作媒体,非著名测试开发,欢迎关注。

  • 相关阅读:
    关于 js 下载PDF文件时
    vue3.0 学习
    iOS
    bootstrap treeview
    SVN版本管理
    js框架
    正则表达式
    如何让安卓手机在电脑上同步显示(MX4 Pro为例)
    mysql 中文乱码
    ADO.NET 数据库连接池大小
  • 原文地址:https://www.cnblogs.com/FunTester/p/14972751.html
Copyright © 2020-2023  润新知