长链接压测-自动化相关实现

1、实现顺序

在这里插入图片描述

2、本地执行某一项目步骤

1. 安装IDEA工具:社区版本地址:

https://www.jetbrains.com/idea/download/#section=mac
在这里插入图片描述

2. 创建Maven项目、命名

在这里插入图片描述

3. pom.xml文件添加本地、或者导入自己的Maven

每个依赖对应添加版本:
<properties>
    <ngrinder.version>3.4.4</ngrinder.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.3.8</spring.version>
    <ws.version>1.5.2</ws.version>
    <fastjson.version>1.2.76</fastjson.version>
    <lombok.version>1.18.20</lombok.version>
    <hutool.version>5.7.0</hutool.version>
    <socket.io.version>1.0.1</socket.io.version>
</properties>

<dependencies>

    <dependency>
        <groupId>org.ngrinder</groupId>
        <artifactId>ngrinder-groovy</artifactId>
        <version>${ngrinder.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>${ws.version}</version>
    </dependency>

    <dependency>
        <groupId>io.socket</groupId>
        <artifactId>socket.io-client</artifactId>
        <version>${socket.io.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>${hutool.version}</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>${fastjson.version}</version>
    </dependency>

</dependencies>

4. 通过IDEA解决spring配置文件

https://blog.csdn.net/yanghanxiu/article/details/79366263

  1. 创建spring-context.xml项目
    路径:/Users/XXX/Documents/某一/src/main/resources/spring-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 导入 RPC 客户端 -->

</beans>

5. 创建java目录下的test项目

冒烟:调通http、websocket、socketio
在这里插入图片描述

6. 单元测试模型

  • 这些方法默认都是public可省略test = new GTest(

在这里插入图片描述

7. 创建Groovy Class例子 + 如何添加断言例子

@Test
    void test() {
        HTTPResponse result = request.POST("http://{host}:20071/api/rest/onLineInfosQuery", params)
        grinder.logger.info("返回值: {}", new String(result.getData(), CharsetUtil.UTF_8))
        String content = result.getText();
        JSONObject obj = JSON.parse(content)
        int code = obj.getIntValue("code")
        if (code != 200) {
            Assert.fail(new String(result.getData(), CharsetUtil.UTF_8))
        } else {
            Assert.assertTrue("接口pass", result.statusCode == 200)
        }
    }

8. 界面调通http-post接口

可通过http://{host}:8080/ngrinder/script/页面添加相应的参数生成Groovy脚本,cpoy到文件里。

9. 本地调整长链接例子:测开调通2个音视频业务接口:信令压测的话测试加入房间和推流

参数实例join_room / {"app_package_name":"com.XXX.apple.rtc","app_version":"1.0.8","brand":"iPhone","c_stream_id":"1623914219902","client_ip":"host","connect_state":0,"continent_code":"AP","country_code":"CN","cpu_arc":"arm64","cpu_model":"arm","device_model":"iPhone10,2","host":1,"is_ipv6":0,"isp":"ChinaUnicom","network_type":"wifi","room_id":"appid-L2391421682680422476618","sdk_version":1020196,"show":"all","sync_role":0,"system_version":"13.700000","timestamp":1623914219905,"user_id":"appid-H2391420624443596874139","user_source":"iOS"}
  1. 音视频长链接

    压测就是socketio建立连接后,模拟客户端向服务端发送数据,服务端支持回调,校验服务端返回值。则一次压测执行成功。

步骤:

  • 创建类实现父类接口,回调接口中申明这些回调状态
class JoinroomPress implements SocketIOCallback{
}

在这里插入图片描述

package com.project.performance.callback

interface SocketIOCallback {

    /**
     * 正确连接
     */
    void connect(Object ...objects)

    /**
     * 连接断开
     */
    void disconnect(Object ...objects)

    /**
     * 连接超时
     */
    void connectTimeout(Object ...objects);

    /**
     * 连接超时
     */
    void connectError(Object ...objects);

    /**
     * 回调事件监听
     */
    void onEventMessage(Object ...objects);

}
  • 主要是在建立长链接的同时对状态进行判断
重点:
client = IO.socket(url,options)
// 客户端建立长链接,几个状态判断
client.on(Socket.EVENT_CONNECT,new Emitter.Listener(){
    @Override
    void call(Object... objects) {
        grinder.logger.info("建立连接成功~",JSON.toJSONString(objects))
        JoinroomPress.this.connect(objects)
    }
})
client.on(Socket.EVENT_DISCONNECT,new Emitter.Listener(){
    @Override
    void call(Object... objects) {
        grinder.logger.info("建立连接失败~",JSON.toJSONString(objects))
        JoinroomPress.this.disconnect(objects)
    }
})
client.on(Socket.EVENT_CONNECT_TIMEOUT,new Emitter.Listener(){
    @Override
    void call(Object... objects) {
        grinder.logger.info("建立连接超时~",JSON.toJSONString(objects))
        JoinroomPress.this.connectTimeout(objects)
    }
})
// 添加业务的校验
client.on(EVENT_RECEIVE_JOIN_SUCCESS,new Emitter.Listener(){
    @Override
    void call(Object... objects) {
        grinder.logger.info("接受服务端 joinSuccess 事件返回消息: {}", JSON.toJSONString(objects))
        JoinroomPress.this.onEventMessage(objects)
    }
} )
  • 建立信令joinroom模版的例子
import cn.hutool.core.date.chinese.SolarTerms
import cn.hutool.core.util.RandomUtil
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.project.performance.callback.SocketIOCallback
import groovy.util.logging.Slf4j
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import net.grinder.script.GTest
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import java.util.concurrent.TimeUnit

import static net.grinder.script.Grinder.grinder

@Slf4j
@RunWith(GrinderRunner)
class JoinroomPress implements SocketIOCallback {

    private static GTest test
    private Socket client
    private static final String url
    private static IO.Options options
    private JSONObject param
    private static final String appId
    private static final String roomId
    private static final String EVENT_RECEIVE_JOIN_SUCCESS
    private static final String EVENT_SEND_JOIN_SUCCESS

    static {
        url = "http://{host}:8883/octopus"
        appId = "appid"
        roomId = appId + "-" + RandomUtil.randomString(25)
        EVENT_RECEIVE_JOIN_SUCCESS = "joinSuccess"
        EVENT_SEND_JOIN_SUCCESS = "join_room"
    }


    @BeforeClass
    static void beforeClass(){
        test = new GTest(1, "调试joinroom信令")
        options = new IO.Options()
        String[] transports = new String[1]
        transports[0] = "websocket"
        options.transports = transports
        grinder.logger.info("执行beforeClass~")

    }
    private Date date

    @BeforeThread
    void beforeThread(){
        test.record(this, "test")
        grinder.statistics.delayReports = true

        param = new JSONObject()
        client = IO.socket(url,options)
        // 客户端建立长链接,几个状态判断
        client.on(Socket.EVENT_CONNECT,new Emitter.Listener(){
            @Override
            void call(Object... objects) {
                grinder.logger.info("建立连接成功~",JSON.toJSONString(objects))
                JoinroomPress.this.connect(objects)
            }
        })
        client.on(Socket.EVENT_DISCONNECT,new Emitter.Listener(){
            @Override
            void call(Object... objects) {
                grinder.logger.info("建立连接失败~",JSON.toJSONString(objects))
                JoinroomPress.this.disconnect(objects)
            }
        })
        client.on(Socket.EVENT_CONNECT_TIMEOUT,new Emitter.Listener(){
            @Override
            void call(Object... objects) {
                grinder.logger.info("建立连接超时~",JSON.toJSONString(objects))
                JoinroomPress.this.connectTimeout(objects)
            }
        })
        // 添加业务的校验
        client.on(EVENT_RECEIVE_JOIN_SUCCESS,new Emitter.Listener(){
            @Override
            void call(Object... objects) {
                grinder.logger.info("接受服务端 joinSuccess 事件返回消息: {}", JSON.toJSONString(objects))
                JoinroomPress.this.onEventMessage(objects)
            }
        } )

        client.connect()
        // 传参数
        param.put("c_stream_id", "appid")
        param.put("app_id", appId)
        param.put("connect_state", 0)
        param.put("room_id", roomId)
        param.put("user_id", appId + "-" + RandomUtil.randomString(33))
        param.put("host", 1)
        param.put("role", "participant")
        param.put("show", "all")
        param.put("user_source", "desktop")
        param.put("sdk_version", 20002)
        param.put("sync_role", 0)
        param.put("app_package_name", "")
        date = new Date()

    }

    @Before
    void before(){
        param.put("user_id", "appid-" + RandomUtil.randomString(33))
        date.setTime(System.currentTimeMillis())
        param.put("timestamp", date)
        // socket是直接连接,音视频业务导致:就是在规定时间500ms,在500ms内如果建立连接就退出,如果没有就继续执行
        long start = System.currentTimeMillis()
        while (System.currentTimeMillis() - start < options.timeout){
            if(client.connected()){
                break
            }
        }
        grinder.logger.info("执行before~")

    }

    @Test
    void test(){
        Assert.assertTrue("连接断开", client.connected())
        param.put("timestamp", date)
        grinder.logger.info("发送数据: {}", param.toJSONString())
        client.emit(EVENT_SEND_JOIN_SUCCESS, param)
        TimeUnit.SECONDS.sleep(1)
        grinder.logger.info("执行test~")

    }

    @After
    void after(){
        if(client.disconnect()){
            client.connect()
        }
        grinder.logger.info("执行after~")

    }

    @Override
    void connect(Object... objects) {

    }

    @Override
    void disconnect(Object... objects) {
        client.connect()
        Assert.fail("连接断开【" + JSON.toJSONString(objects) + "】")
    }

    @Override
    void connectTimeout(Object... objects) {
        Assert.fail(JSON.toJSONString(objects))
    }

    @Override
    void connectError(Object... objects) {
        Assert.fail(JSON.toJSONString(objects))
    }

    @Override
    void onEventMessage(Object... objects) {
        JSONObject obj = JSON.parseObject(JSON.toJSONString(objects[0]))
        int code = obj.getIntValue("code")
        Assert.assertTrue(JSON.toJSONString(objects), code == 200)
    }
}
  1. IM TCP压测(IM的chat通信实现)

就是使用WS建立建立连接,调用客户端的方法,给服务端发送多种消息类型,服务端给callback回调(开发待添加),校验是否成功。

  • 步骤:

  • 导入jar压缩包依赖
    在这里插入图片描述

  • pom.xml引入新增依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.65.Final</version>
</dependency>

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.17.3</version>
</dependency>
  • 建立连接 + 调用发送方法
@Test
void testImChat(){
    String message = messagePre + RandomUtil.randomString(10)
    boolean result = client.chat(targetUserId, messagePre + RandomUtil.randomString(10))
    Assert.assertTrue("【" + userId + "】发送数据【" + message + "】到【" + targetUserId + "】失败", result)
}

  • 完整代码逻辑
package com.project.performance.test.cmim

import cn.hutool.core.util.RandomUtil
import com.linkv.im.core.IMEventCallBack
import com.linkv.im.core.IMOnMessageCallback
import com.linkv.im.core.LinkVIMClient
import groovy.util.logging.Slf4j
import net.grinder.script.GTest
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.AfterThread
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

import java.util.concurrent.TimeUnit

import static net.grinder.script.Grinder.grinder

@Slf4j
@RunWith(GrinderRunner)
class IMChatTest implements IMOnMessageCallback, IMEventCallBack {

    private static GTest test

    private static String host

    private static int port

    private static String appId

    private static String messagePre

    @BeforeProcess
    static void beforeProcess() {
        test = new GTest(1, "压测 IM-Chat 私聊")
        host = "{host}"
        port = 10001
        appId = "cccc_tob"
        messagePre = "Press Test -- "
    }

    private LinkVIMClient client

    private String userId

    private String deviceId

    private String targetUserId

    @BeforeThread
    void beforeThread() {
        test.record(this, "testImChat")
        grinder.statistics.delayReports = true
        client = new LinkVIMClient(appId, host, port)
        userId = "self" + RandomUtil.randomString(5)
        deviceId = RandomUtil.randomString(10)
        client.connection(this, this)
        targetUserId = "1234"
    }

    @AfterThread
    void afterThread(){
        client = null
    }

    private boolean isLogin = false

    @Before
    void beforeTest(){
        while (true){
            if(isLogin){
                break
            }
            TimeUnit.MILLISECONDS.sleep(10)
        }
    }

    @Test
    void testImChat(){
        String message = messagePre + RandomUtil.randomString(10)
        boolean result = client.chat(targetUserId, messagePre + RandomUtil.randomString(10))
        Assert.assertTrue("【" + userId + "】发送数据【" + message + "】到【" + targetUserId + "】失败", result)
    }

    @After
    void afterTest(){
        if(!isLogin){
            client.connection(this, this)
        }
    }

    @Override
    void callBack(String event, int status) {
        if (event.equals("connetion") && status == 0) {
            client.login(userId, deviceId)
        } else if(event.equals("loginSendMessage") && status == 0){
            isLogin = true
        } else {
            isLogin = false
            client.connection(this, this)
        }
    }

    @Override
    void onEvent(String messageType, Long messageId, String content) {
        if(messageType == "ack" && messageId != 0){
            log.info("获取发送消息回执: {}", content)
            Assert.assertTrue(content, content.contains(userId))
        }
    }
}

  • 将调通的代码粘贴到前端脚本,执行使用即可。

3、问题解决记录

  1. Maven需要加载,执行位置,爆红直接都加载即可
    在这里插入图片描述

  2. 每调试一条case报错需要添加依赖
    在这里插入图片描述

  3. Maven依赖的版本号确定

查询地址:https://mvnrepository.com/
在这里插入图片描述

  1. 因为前端添加了Maven依赖,每次执行压测都是将依赖包打包传到代理服务器上去进行压测,所以有等待压测的状态。
    在这里插入图片描述

  2. 本地也可使用python,对应Jpthon

  3. 因为音视频服务端的socket.io是2以下版本,不支持,才使用的Java语言,对应的Groovy。
    在这里插入图片描述

  4. 了解压测report

  1. 成功、失败统计
  2. 均值
  3. 峰值
  4. 每秒执行的条数

在这里插入图片描述

  1. 将本机配置成一台Agent方法
 1. 将ngrinder-agent-3.4.4-{host}-mzhuo.tar文件下载到本地
 2. 解压之后:tar -zxvf ngrinder-agent-3.4.4-{host}-mzhuo.tar
 3. 进入该目录:cd ngrinder-agent/
 4. 本地启动进程:./run_agent.sh
 5. 则在设置配置的页面,选择的Agent变为max:3
 6. 执行关闭进程则恢复:./stop_agent.sh

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值