性能测试中集合点和多阶段同步问题初探

2021-01-05 10:01:20 浏览数 (1)

在性能测试过程中,场景会遇到一些场景,需要在各种准备条件满足的情况下才能开始进行性能测试。例如:我需要200个学生进行一个单接口的压测,首先我得让这200个学生都登录,然后才能用200个User对象发起接口请求。

在以往的经历中,我更偏向于串行的方式去登录200个学生,然后再创建压测ThreadBase对象进行性能测试,由于前期消耗时间比较短,也就一直没有进行优化。在学习了CyclicBarrier类在性能测试中应用之后,一直有个想法就是将这段准备时间压缩,多线程去执行,然后在某一个时刻再开始压测任务的执行。基本思路就是使用Java多线程编程类CyclicBarrier解决性能测试中多线程的集合点设置和多阶段性能测试问题。

这里我用了一个链路压测的脚本进行改造,原版的脚本大家可以参考:链路压测中如何记录每一个耗时的请求,如果对链路压测感兴趣的还可以参考以前的文章:

  • 如何同时压测创建和删除接口
  • 绑定手机号性能测试
  • 手机号验证码登录性能测试
  • 单点登录性能测试方案

思路

在创建ThreadBase对象的时候,引入一个线程安全类AtomicInteger来标记每一个线程使用的用户账号和密码,区分不同线程使用不同的用户。

集合点的设置,我设置了两处:一是执行before(),用于等待所有线程的用户基类OkayBase和用户业务模块类OKclass对象初始化。二是在执行after()方法中,用于标记一下所有线程执行完毕,这里有些多余,因为在ThreadBase中,我设置了一个CountDownLatch对象的属性,用于等待所有线程任务执行完毕。这里只是为了演示一下CyclicBarrier多阶段同步的使用。

ThreadBase类的after()方法内容如下:

代码语言:javascript复制
    /**
     * 运行待测方法后的处理
     */
    protected void after() {
        if (countDownLatch != null)
            countDownLatch.countDown();
    }

Demo实践

代码中我注释掉了实际的业务请求,只写了两个sleep()方法和一个日志输出。

代码语言:javascript复制
package com.okayqa.composer.performance.master1_0

import com.fun.base.constaint.ThreadLimitTimesCount
import com.fun.frame.execute.Concurrent
import com.fun.frame.httpclient.ClientManage
import com.fun.utils.ArgsUtil
import com.okayqa.composer.base.OkayBase
import com.okayqa.composer.function.OKClass
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.util.concurrent.CyclicBarrier
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger

class BothCollectCopy extends OkayBase {

    private static Logger logger = LoggerFactory.getLogger(BothCollectCopy.class)

    static AtomicInteger u = new AtomicInteger(0)

    static int times = 0

    static int thread

    static CyclicBarrier cyclicBarrier

    public static void main(String[] args) {
        ClientManage.init(5, 1, 0, "", 0)
        def util = new ArgsUtil(args)
        thread = util.getIntOrdefault(0, 5)
        times = util.getIntOrdefault(1, 2)

        cyclicBarrier = new CyclicBarrier(thread, new Runnable() {

            @Override
            void run() {
                logger.info("到达人数 {} ", cyclicBarrier.getParties())

            }
        })

        def funs = []
        thread.times {
            funs << new Fun()
        }
        new Concurrent(funs, "收藏和取消收藏").start()
        allOver()
    }

    static int getTimes() {
        return times
    }

    static class Fun extends ThreadLimitTimesCount {

        OkayBase okayBase

        OKClass driver

        public Fun() {
            super(null, getTimes(), null)
        }

        @Override
        void before() {
            super.before()
            okayBase = getBase(u.getAndIncrement())
            driver = new OKClass(okayBase)
            cyclicBarrier.await(10, TimeUnit.SECONDS)
        }

        @Override
        protected void after() {
            super.after()
            cyclicBarrier.await(100,TimeUnit.SECONDS)
        }

        @Override
        protected void doing() throws Exception {
//            def collect = driver.collect()
//            def value = okayBase.getLastRequestId()   CONNECTOR
//            this.threadmark  = value
//            if (collect.getJSONObject("meta").getIntValue("ecode") != 0) fail(value   "请求出错!")
//            def collect1 = driver.unCollect()
//            def value1 = okayBase.getLastRequestId()
//            this.threadmark  = value1
//            if (collect1.getJSONObject("meta").getIntValue("ecode") != 0) fail(value1   "请求出错!")
            sleep(1.0)
            logger.info(this.threadName)
            sleep(1.0)
        }
    }

}

控制台输出

代码语言:javascript复制
INFO-> 当前用户:fv,IP:10.60.192.21,工作目录:/Users/fv/Documents/workspace/okay_test/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> requestid: Fdev16093173246753
INFO-> requestid: Fdev16093173245764
INFO-> requestid: Fdev16093173244862
INFO-> requestid: Fdev16093173243871
INFO-> requestid: Fdev16093173248887
INFO-> 请求uri:https://teacherpad-d***/api/t_pad/user/login,耗时:453 ms
INFO-> 请求uri:https://teacherpad***.cn/api/t_pad/user/login,耗时:433 ms
INFO-> 教师:61951375269,学科:null,名称:61951375269,登录成功!
INFO-> 教师:61951377260,学科:null,名称:61951377260,登录成功!
INFO-> 请求uri:https://teacherpad***n/api/t_pad/user/login,耗时:459 ms
INFO-> 教师:61951377259,学科:null,名称:61951377259,登录成功!
INFO-> 请求uri:https://teacherpad**cn/api/t_pad/user/login,耗时:488 ms
INFO-> 教师:61951375951,学科:null,名称:61951375951,登录成功!
INFO-> 请求uri:https://teacherpad-**cn/api/t_pad/user/login,耗时:690 ms
INFO-> 教师:61951377261,学科:null,名称:61951377261,登录成功!
INFO-> 到达人数 5 
INFO-> 收藏和取消收藏0
INFO-> 收藏和取消收藏2
INFO-> 收藏和取消收藏3
INFO-> 收藏和取消收藏1
INFO-> 收藏和取消收藏4
INFO-> 收藏和取消收藏4
INFO-> 收藏和取消收藏0
INFO-> 收藏和取消收藏2
INFO-> 收藏和取消收藏1
INFO-> 收藏和取消收藏3
INFO-> 线程:收藏和取消收藏1,执行次数:2,错误次数: 0,总耗时:5 s
INFO-> 线程:收藏和取消收藏0,执行次数:2,错误次数: 0,总耗时:5 s
INFO-> 线程:收藏和取消收藏4,执行次数:2,错误次数: 0,总耗时:5 s
INFO-> 线程:收藏和取消收藏3,执行次数:2,错误次数: 0,总耗时:5 s
INFO-> 线程:收藏和取消收藏2,执行次数:2,错误次数: 0,总耗时:5 s
INFO-> 到达人数 5 
INFO-> 总计5个线程,共用时:4.925 s,执行总数:10,错误数:0,失败数:0
INFO-> 数据保存成功!文件名:/Users/fv/Documents/workspace/okay_test/long/data/5收藏和取消收藏20201230163524
INFO-> 数据保存成功!文件名:/Users/fv/Documents/workspace/okay_test/long/mark/收藏和取消收藏20201230163524
INFO-> 
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
>  {
>  ① . "rt":2006,
>  ① . "total":10,
>  ① . "qps":2.491528802072952,
>  ① . "failRate":0.0,
>  ① . "threads":5,
>  ① . "startTime":"2020-12-30 16:35:24",
>  ① . "endTime":"2020-12-30 16:35:28",
>  ① . "errorRate":0.0,
>  ① . "executeTotal":10,
>  ① . "mark":"收藏和取消收藏20201230163524",
>  ① . "table":""
>  }
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
INFO-> 

Process finished with exit code 0

  • 简直完美!哈哈哈!!!

「FunTester」,非著名测试开发,文章记录学习和感悟,欢迎关注,交流成长。

0 人点赞