分布式压测机在自动化平台中的应用

本文介绍了一款自动化分布式压测平台,旨在简化压测过程,提高效率。平台采用前后端分离设计,前端基于Vue,后端基于SpringBoot,实现一键式压测机部署与执行,大幅减少手动操作,提升压测效率十倍以上。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言
      不知道大家有没有这种痛点,我们公司的服务都是云化的(阿里云、华为云);所以在有些业务场景必须应用到压测技术,所以按照公司惯用流程需要申请虚拟机作为压测机使用,而且还不是一台,可能5台、10台;甚至以上。用完了在让运维回收,领导曰:节约公司成本,虚拟机贵,what fuck?
      如果是这种情况的话,大家可能考虑到一个问题,那就是有压测需求要申请机器,申请完后需要手动去搭建机器,然后用完释放。所以再这样的闭环条件下其实大家可以看到里面的很多工作都是可以用自动化去完成的。在这里大家可能又会有另一种想法那就是写一个脚本自动去做这件事情,但是又有另一种问题;脚本还是存在一种局限性,如何让它更自动化?或者说更加白痴化;通过一个小白就知道怎么去做这件事情,而不依赖于操作人需要任何技术,那么自动化平台整合分布式压测机那就是很好的一个产物,将自动化思想打造成一个平台化的应用让用户去操作,好了话不多说开始介绍核心思路

二、操作流程

  1. 首先用户登录到平台,进行参数配置
    在这里插入图片描述

录制参数解析:
1.压测名称,根据自己这次压测的业务而定,没有太多含义
2.类型:支持分布式压测、单机压测
3.masterIp:若为分布式压测masterIp作为操控机,若为单机压测masterIp作为负载机
4.slaveIp:若为分布式压测slaveIp作为负载机,若为单机压测此项会前端自动隐藏,无需填

  1. 录制成功后可以进行修改、删除操作,这个比较简单,大家看下图就行
    在这里插入图片描述
  2. 上传操作需要将录制好的jmeter脚本上传,jmx格式文件
    在这里插入图片描述
  3. 录制完成之后,点击执行操作,后面自动化帮你部署搭建分布式压测机,并执行压测生成报告
  4. 压测报告实时查看
    在这里插入图片描述

这样操作系数就达到白痴化水准,用户无需技术可言即可完成自动化的分布式压测机的搭建以及执行操作,并能够实时获取压测报告

三、设计技术详解

  • 首先该平台是一个前后端分离的项目,前端vue、后端springboot

1.前端技术点:vue、axios、elementUI、echarts、mockjs、router、webpack
详情参考github开源地址:https://github.com/henryxiaolovejava/autoplatform-web
2.后端技术点:springweb、aspectj、redis、jwt、mybatisplus、springmvc、log4j2、fastjson、freemarker、javamail、beanmapper
详情参考github开源地址:https://github.com/henryxiaolovejava/autoplatform-backend
- 技术图解
在这里插入图片描述
- 代码详解

  1. 首先点击执行按钮调用后端接口开始执行,先进入后端controller层
    @PostMapping("/press/excutePressScript")
    public ResponseResult excutePressScript(PressConfigInfoAO pressConfigInfoAO) {
    	//调用service层处理逻辑
        Boolean isSuccess = pressConfigBaseService.executePressScript(pressConfigInfoAO);
        if (isSuccess) {
            return new ResponseResult(ResponseCodeEnums.SUCCESS);
        }
        return new ResponseResult(ResponseCodeEnums.FAIL);
    }
  1. 调用service层
    @Resource
    private PressConfigInfoMapper pressConfigInfoMapper;

    @Resource
    private PressExecuteRecordMapper pressExecuteRecordMapper;

    @Resource
    private AsyncExecuteShellOperation shellOperation;

    public Boolean executePressScript(PressConfigInfoAO pressConfigInfoAO) {
        QueryWrapper<PressConfigInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", pressConfigInfoAO.getId());
        //从mysql找到执行该条数据的配置信息
        PressConfigInfo configInfo = pressConfigInfoMapper.selectOne(queryWrapper);
        //若该配置属于脚本jmx未上传状态,则抛出异常无法继续往下执行
        if (configInfo.getScriptStatus() == BaseInfoConstant.NUMBER_ZERO) {
            throw new BizException(ResponseCodeEnums.FAIL);
        }
        //校验压测记录表是否有未跑完的case
        QueryWrapper<PressExecuteRecord> recordWrapper = new QueryWrapper<>();
        recordWrapper.eq("press_id",pressConfigInfoAO.getId());
        recordWrapper.orderByDesc("ctime").last("limit 1");
        PressExecuteRecord record1 = pressExecuteRecordMapper.selectOne(recordWrapper);
        if (!StringUtils.isEmpty(record1)) {
        	//若存在最近未跑完的case则抛出异常,无法继续往下执行
            if (record1.getStatus() == BaseInfoConstant.NUMBER_ZERO){
                throw new BizException(ResponseCodeEnums.EXECUTE_ERROR);
            }
        }
		//生成一条压测记录
        PressExecuteRecord record = new PressExecuteRecord();
        record.setPressId(pressConfigInfoAO.getId());
        record.setPressName(pressConfigInfoAO.getPressName());
        record.setMasterIp(pressConfigInfoAO.getMasterIp());
        //若为单机压测slaveIp无需设值,反之需要设值
        if (pressConfigInfoAO.getType() == BaseInfoConstant.NUMBER_ONE) {
            record.setSlaveIp(pressConfigInfoAO.getSlaveIp());
        }
        record.setType(pressConfigInfoAO.getType());
        record.setScriptName(pressConfigInfoAO.getScriptName().trim());
        record.setExecutor(pressConfigInfoAO.getExecutor());
        boolean isSuccess = pressExecuteRecordMapper.insert(record) > 0 ? true : false;
        //异步操作执行shell脚本
        shellOperation.executeShellScript(pressConfigInfoAO, record.getId());
        if (isSuccess) {
            return true;
        }
        return false;
    }
  1. 异步操作的方法解析
@Slf4j
public class AsyncExecuteShellOperation {

    @Value("${execute.shell.script}")
    private String shellPath;

    @Resource
    private PressExecuteRecordMapper pressExecuteRecordMapper;

    @Async
    public void executeShellScript(PressConfigInfoAO pressConfigInfoAO, Long id) {
        List<String> scripts = new ArrayList<>();
        StringBuffer hostBuffer = new StringBuffer();
        scripts.add(BaseInfoConstant.BASH);
        scripts.add(shellPath);
        //参数masterIp
        String masterIp = pressConfigInfoAO.getMasterIp().trim();
        //参数scriptName
        String scriptName = pressConfigInfoAO.getScriptName().trim();

        int status = 0;
        if (pressConfigInfoAO.getType() == BaseInfoConstant.NUMBER_ZERO){
            //单机压测
            try {
                //参数masterIp
                scripts.add(masterIp);
                //参数slaveIp
                scripts.add("null");
                scripts.add(scriptName);
                String[] executor = scripts.toArray(new String[0]);
                //执行shell脚本
                Process exec = Runtime.getRuntime().exec(executor);
                status = exec.waitFor();
                if (status != BaseInfoConstant.NUMBER_ZERO) {
                    log.error("脚本执行失败");
                    throw new BizException(ResponseCodeEnums.SCRIPT_ERROR);
                }else {
                    log.info("脚本执行成功");
                    PressExecuteRecord record = new PressExecuteRecord();
                    record.setId(id);
                    record.setStatus(BaseInfoConstant.NUMBER_ONE);
                    pressExecuteRecordMapper.updateById(record);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }else{
            //分布式压测
            try {
                //参数hosts
                String slaveIps = pressConfigInfoAO.getSlaveIp().trim();
                String[] hosts = slaveIps.split(",");
                for (String host: hosts){
                    hostBuffer.append(host).append(":1099").append(",");
                }
                String newHosts = hostBuffer.substring(0, hostBuffer.length() - 1);

                scripts.add(masterIp);
                scripts.add(slaveIps);
                scripts.add(scriptName);
                scripts.add(newHosts);
                String[] executor = scripts.toArray(new String[0]);

                System.out.println("success");
				//执行shell脚本
                Process exec = Runtime.getRuntime().exec(executor);
                status = exec.waitFor();
                if (status != BaseInfoConstant.NUMBER_ZERO) {
                    log.error("脚本执行失败");
                    throw new BizException(ResponseCodeEnums.SCRIPT_ERROR);
                }else {
                    log.info("脚本运行成功");
                    PressExecuteRecord record = new PressExecuteRecord();
                    record.setId(id);
                    record.setStatus(BaseInfoConstant.NUMBER_ONE);
                    pressExecuteRecordMapper.updateById(record);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

在调用后端执行接口的整体逻辑大概是这样,有疑惑的可以参考github源码

  1. shell脚本执行内容解析

后端执行脚本若为单机压测传入三个参数:masterIp、slaveIp、scriptName(slaveIp默认传null)
若为分布式压测传入是个参数:masterIp、slaveIp、scriptName、hosts

#!/bin/bash

#
masterIp=$1
slaveIp=$2
scriptName=$3
hosts=$4

echo $hosts

#定义基础配置路径
scriptPath=/data/workspace/backend/scriptFiles
packagePath=/data/workspace/backend/package
targetPath=/root/jmeter5/bin
reportPath=/data/workspace/tomcat/tomcat8/webapps/press
reportName=`echo $scriptName | awk -F"." '{print $1}'`

echo $reportName

#单机压测
if [ $slaveIp == "null" ];
then
  #单机压测方式
  nohup ssh $masterIp "rm -rf /root/jmeter5"
  nohup scp -r $packagePath/jmeter5 $masterIp:/root
  nohup scp -r $scriptPath/$scriptName $masterIp:$targetPath
  nohup ssh $masterIp "cd $targetPath;bash jmeter.sh -n -t $scriptName -l ./result.jtl -e -o ./$reportName"
  nohup scp -r $masterIp:$targetPath/$reportName $reportPath
else
  #分布式压测
  #masterIp部署
  #前置处理;删除之前可能存在的配置
  nohup ssh $masterIp "rm -rf /root/jmeter5"
  #将jmeter文件传到操控机
  nohup scp -r $packagePath/jmeter5 $masterIp:/root
  #将之前录制的jmx文件传入到操控机
  nohup scp -r $scriptPath/$scriptName $masterIp:$targetPath
  #初始化操控机的配置,用于操控负载机
  nohup ssh $masterIp "cd $targetPath;sed -i 's/remote_hosts=127.0.0.1/remote_hosts=$hosts/g' jmeter.properties"
  
  #slaveIp部署
  #获取到slaveIp中的每个IP并对其遍历操作
  array=(${slaveIp//\,/ })
  for i in "${!array[@]}"; do
    slaveIp=`echo ${array[i]}`
    echo $slaveIp
    #将jmeter文件传到每个负载机
    nohup scp -r $packagePath/jmeter5 $slaveIp:/root
    ssh $slaveIp "cd $targetPath;nohup bash jmeter-server -Djava.rmi.server.hostname=$slaveIp > /dev/null 2>&1 &"
  done

  #执行压测
  sleep 10
  nohup ssh $masterIp "cd $targetPath;bash jmeter.sh -n -t $scriptName -l ./result.jtl -r -e -o ./$reportName"
  #报告部署到Tomcat
  nohup scp -r $masterIp:$targetPath/$reportName $reportPath

fi

四、优势

  • 如果你的公司性质跟我差不多,为了节约资源需要每次申请机器、用完回收,那么这套平台是非常符合你的口味
  • 该平台避免了认为频繁操作的苦恼,并且认为操作更加容易出错
  • 同样我个人实际比对过,这套平台比人为手动部署执行效率在时间上缩短了至少10倍以上

如果你有同样的痛点,希望我的这篇文章能够帮助到你,如有问题可以私信联系本人,不胜解答!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值