性能测试-项目实战-接口全流程压测

article/2025/11/7 11:12:49

目录

1、前言

1.1、项目背景

1.2、场景执行步骤

2、司机长链接

2.1、司机出车环境

2.2、主要用到的包

2.3、脚本解析

3、全流程压测脚本

3.1、司乘数据准备

3.2、全链路压测脚本

4、资源监控与收集

4.1、聚合报告

4.2、自研脚本


1、前言

1.1、项目背景

在车辆与用户数的日益增长情况下,避免日后系统数据增长可能带来的系统瓶颈,确保多用户访问不会出现问题,特针对现有重要代表性接口以及全流程进行压力测试。

1.2、场景执行步骤

针对全流程压测:

1、先开启司机长链接脚本,取保司机在线。

2、查看redis,查询司机接口,确认多少司机在线(达到满足压测条件)。

3、执行接口全流程压测脚本。

4、服务器性能监控与数据收集(阿里云、Jmeter聚合报告、自研脚本)。

2、司机长链接

模拟司机出车,需要开发辅助脚本。由于之前有Java版,但供组内使用时发现不太方便,所以重新开发一版(Python版本)。

2.1、司机出车环境

模拟司机出车环境:测试环境、测试环境2、预发环境

模拟司机出车.py:模拟一个司机出车

模拟司机出车(多线程).py:模拟多个司机出车

模拟司机出车(多线程)-压测.py:模拟多个司机出车,主要用于压测任务,在指定坐标点范围内随机生成坐标出车

2.2、主要用到的包

脚本开发过程中主要用到的包:socket、threading、requests、MySQLdb、redis、pandas

2.3、脚本解析

1、模拟司机出车的主要动作就是:司机注册(司机在线)和上传坐标(司机位置)

司机注册(部分代码):

上传坐标(部分代码):

2、调用函数(部分):进制转换、随机生成坐标

进制转换(部分代码):

# 字符串转16进制
def str_hex(mystr):str1_16 = ":".join("{:02x}".format(ord(c)) for c in mystr)# print(str1_16)str2_16 = str1_16.replace(':', '\\x')str3_16 = "\\x"+str2_16# print(str3_16)return str(str3_16)# 10进制转16进制
def dec_hex(str_10, number):str_16 = str(hex(int(str_10)))# print(str_16)str2_16 = str_16.replace("0x", '')# number为16进制长度str3_16 = str2_16.rjust(number,'0')# print(str3_16)str4_16_string = ""for i_str3 in range(len(str3_16)//2):# print(i_str3)result = "\\x" + str3_16[i_str3*2:i_str3*2+2]# print(result)str4_16_string += result# print(str4_16_string)return str(str4_16_string)'''
十六进制字符串转bytes
eg:
'01 23 45 67 89 AB CD EF 01 23 45 67 89 AB CD EF'
b'\x01#Eg\x89\xab\xcd\xef\x01#Eg\x89\xab\xcd\xef'
'''
def hexStringTobytes(strTobytes):toBytes = strTobytes.replace(" ", "")# print(toBytes)# print(bytes.fromhex(toBytes))return bytes.fromhex(toBytes)

随机生成坐标(代码):

# 随机生成范围内经纬度坐标(base_log:经度基准点,base_lat:维度基准点,radius:距离基准点的半径)
def randomLogLat(base_log = None, base_lat = None, radius = None):radius_in_degrees = radius / 111300u = float(random.uniform(0.0, 1.0))v = float(random.uniform(0.0, 1.0))w = radius_in_degrees * math.sqrt(u)t = 2 * math.pi * vx = w * math.cos(t)y = w * math.sin(t)longitude = y + base_loglatitude = x + base_lat# 这里是想保留6位小数loga = '%.6f' % longitudelata = '%.6f' % latitudereturn loga, lata

3、程序运行主体代码

模拟司机出车.py

# 程序运行主体
var = 1
while var == 1:# 选择环境env = input("请选择环境:1(测试环境)、2(测试环境2)、3(预发环境):")if env == "1" or env == "2" or env == "3":if env == "1":environment = "test"elif env == "2":environment = "test2"elif env == "3":environment = "uat"phone = input("请输入司机端手机号:")# 手机号格式校验:ret = re.match(r"^1[235678]\d{9}$", phone)if ret:coordinate = input("请输入经纬度坐标 例如 116.321895, 39.966849:")strlist = coordinate.split(',')longitude = strlist[0].strip()latitude = strlist[1].strip()# print(longitude)# print(latitude)# # 保留6位小数# print(format(float(longitude), '.6f'))# print(format(float(latitude), '.6f'))iteration = input("请输入司机上线迭代次数(5秒一次迭代)需大于0:")# 判断输入的是否为整数且不为0if (iteration.isdigit() == True) and (int(iteration) != 0):# 司机长链接runLonglink(environment, phone, format(float(longitude), '.6f'), format(float(latitude), '.6f'), iteration)else:print("输入迭代次数格式错误\n")else:print("手机号格式错误\n")else:print("选择环境不对\n")

模拟司机出车(多线程).py

# 程序运行主体
if __name__=="__main__":threads = []t1 = threading.Thread(target=runLonglink, args=("test", 137xxxxxxxx, 116.321423,39.966684, 50)) threads.append(t1)t2 = threading.Thread(target=runLonglink, args=("test", 137xxxxxxxx, 116.321315,39.967362, 300)) threads.append(t2)t3 = threading.Thread(target=runLonglink, args=("test", 137xxxxxxxx, 116.321576,39.966901, 300)) threads.append(t3)t4 = threading.Thread(target=runLonglink, args=("test", 137xxxxxxxx, 116.354229,40.008462, 300)) threads.append(t4)for t in threads:t.start()for t in threads:t.join()print("执行完毕")

模拟司机出车(多线程)-压测.py

出车的司机数据存放到了Dtoken.csv文件里。

# 程序运行主体
if __name__=="__main__":threads = []# 获取CSV文件token_data = pandas.read_csv('Dtoken.csv', sep=',', header=None)print("出车司机个数 " + str(token_data.count().values[0]))# 逐行读取for index in token_data.index:# print(index)token_data_driverID = str(token_data.loc[index].values[0])token_data_driverToken = str(token_data.loc[index].values[1])# print(token_data_driverID)# print(token_data_driverToken)# 随机生成范围内经纬度坐标random_longitude, random_latitude = randomLogLat(base_log=116.321407, base_lat=39.966886, radius=1000)print(random_longitude + "," + random_latitude)t_Longlink = threading.Thread(target=runLonglink, args=(index + 1, "test", token_data_driverID, token_data_driverToken, random_longitude, random_latitude, 10))threads.append(t_Longlink)for t in threads:t.start()for t in threads:t.join()print("执行完毕")

Dtoken.csv文件(存放司机ID、司机Token)。

4、执行脚本

例如:模拟司机出车.py

3、全流程压测脚本

3.1、司乘数据准备

ID(乘客/司机)、Token,是每个接口都会用到的,所以压测前先把这些基础数据准备完成。

1、获取乘客ID和Token

根据乘客手机号(参数化),发送验证码(可设置通用验证码,跳过此步),进行登录,并对接口返回进行提取乘客ID和Token,保存到指定文件里。

2、获取司机ID和Token

根据司机手机号(参数化),发送验证码(可设置通用验证码,跳过此步),进行登录,并对接口返回进行提取司机ID和Token,保存到指定文件里。

3、动态获取验证码(代码):

import redis.clients.jedis.Jedis;
import org.apache.commons.lang3.StringUtils;String host = "XXX"; //服务器地址
int port = 6379; //端口号
String password = "XXX"; //redis密码
int index = 1; //redis db
String key = "XXX_135XXXXXXXX";Jedis jedis = new Jedis(host, port);
if(StringUtils.isNotBlank(password)){jedis.auth(password);}
jedis.select(index); //选择redis dbSet keys = jedis.keys(key); 
log.info("keys:  "+keys);code = jedis.get(keys.iterator().next()); //获取key的值
vars.put("code",code.replace("\"", "")); //将key的值保存为变量

4、写文件(代码):

FileWriter fstream = new FileWriter("D:\\xxx.txt",true);
BufferedWriter out= new  BufferedWriter(fstream);
BufferedWriter bw = null;
out.write(vars.get("id")+","+vars.get("token")+"\r\n");
out.close();
fstream();

3.2、全链路压测脚本

脚本主要分为两大块:服务中订单和创建订单。

1、服务中订单(对司机之前未完成的订单和乘客未支付的订单的处理)

例如:已经派单,循环判断接单状态,失败(派单失败)或者成功(司机接到派单)。

如派单成功,接着按流程走(去接乘客、到达乘客上车点、开始计费、到达目的地、发起收款、乘客支付、乘客评价等)。

2、创建订单:

分为3个分支(获取预估价格失败、叫车成功、叫车失败)

例如:叫车成功,循环判断接单状态,失败(派单失败)或者成功(司机接到派单)。

如司机接单成功,接着流程走(去接乘客、到达乘客上车点、开始计费、到达目的地、发起收款、乘客支付、乘客评价等)。

4、资源监控与收集

Jmeter进行全流程压测时,可以使用阿里云、Jmeter聚合报告、自研脚本等对服务器性能监控与数据收集。

4.1、聚合报告

在Jmeter脚本里添加:察看结果树、聚合报告。

聚合报告

察看结果树

Jmeter常用术语:

(1)线程数:并发用户数。

(2)请求数Samples:发出了多少个请求,例:模拟10个用户,每个用户迭代10次,就是100次。

(3)平均响应时间Average:单个请求平均响应时间(毫秒)。

(4)中位数Median: 50% 用户的响应时间(毫秒)。

(5)90% Line:90% 用户的响应时间。

(6)Min:最小响应时间(毫秒)。

(7)Max:最大响应时间(毫秒)。

(8)错误率Error%:出现错误的请求的数量/请求的总数。

(9)吞吐量Throughput:表示每秒完成的请求数(Request per Second)。

4.2、自研脚本

在整个司乘订单状态流转过程中,想监控一下这些状态,开发了实时查询司机在线、发单、接单状态脚本。

脚本生成图形使用matplotlib包。

脚本大概流程:从redis获取司机在线数,并且通过查询数据库中订单的状态,绘制订单状态图(实时)。

程序运行主体代码:

# 选择环境
environment = input("选择环境:1(测试环境)、2(压测环境):")
if environment == "1" or environment == "2":plt.ion()plt.figure(1)number = [0,]driver_online = [0,]status_100 = [0,]status_200 = [0,]status_300 = [0,]status_310 = [0,]status_320 = [0,]status_330 = [0,]status_400 = [0,]# 当前时间t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())for i in range(1000000):number.append(i + 1)# print(number)online = int(get_driver_online(environment))s100 = int(get_status_100_count(environment, t))s200 = int(get_status_200_count(environment, t))s300 = int(get_status_300_count(environment, t))s310 = int(get_status_310_count(environment, t))s320 = int(get_status_320_count(environment, t))s330 = int(get_status_330_count(environment, t))s400 = int(get_status_400_count(environment, t))driver_online.append(online)# print(driver_online)status_100.append(s100)# print(status_100)status_200.append(s200)# print(status_200)status_300.append(s300)# print(status_300)status_310.append(s310)# print(status_310)status_320.append(s320)# print(status_320)status_330.append(s330)# print(status_330)status_400.append(s400)# print(status_400)plt.title('result')plt.grid(linestyle="--") # 设置背景网格线为虚线# color:b:blue、g:green、r:red、c:cyan、m:magenta、y:yellow、k:black、w:whiteplt.plot(number, status_100, color='green', label='status-100 CREATE')plt.plot(number, status_200, color='blue', label='status-200 DRIVER_TAKING')plt.plot(number, status_300, color='cyan', label='status-300 END')plt.plot(number, status_310, color='magenta', label='status-310 PAY')plt.plot(number, status_320, color='yellow', label='status-320 CANCEL')plt.plot(number, status_330, color='peru', label='status-330 DISPATCH_FAIL')plt.plot(number, status_400, color='orange', label='status-400 CLOSE')plt.plot(number, driver_online, color='red', label='driver-online')handles, labels = plt.gca().get_legend_handles_labels()by_label = OrderedDict(zip(labels, handles))plt.legend(by_label.values(), by_label.keys())plt.xlabel('iterations')plt.ylabel('amount')# plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果要显示中文字体,则在此处设为:SimHeiplt.show()plt.pause(0.3)print("driver-online: " + str(online) + " | status-100: " + str(s100) + " | status-200: " + str(s200) + " | status-300: " + str(s300) + " | status-310: " + str(s310) + " | status-320: " + str(s320) + " | status-330: " + str(s330) + " | status-400: " + str(s400))
else:print("选择环境不对")

如图所示:运行时的订单流转状态图(10个司机出车,还没有接单)。


http://chatgpt.dhexx.cn/article/YKLNEO4S.shtml

相关文章

浅谈接口压测

一.背景 saas版在经过项目组全体成员的不断努力,最终实现了第一版的上线,为了验证sass系统可以支持多少用户同时在线访问,产生了sass版的接口压测。 二.压测是什么为什么要做压测 压测一般指性能压力测试,通过压测可以了解系统…

数据库select语句详解

SELECT 1.基本语法 select * from 表名 查询这张表所有内容。select 列名 from 表名 查询这张表某一列所有内容。select 列名1,列名2…from 表名 查询这张表的列1,列2,等多列。select distinct 列名 from 表名 查询这一列去掉重复内容后的内…

SQLite基本语句

SQLite基本语句 一: 1.SQLite数据库是一种嵌入式数据库,它的数据就是一个scores.db 2.经常被集成到各种应用程序中,甚至ios、Android、Mac OS、Linux 3.Python中内置了SQLite数据库,直接使用 4.数据库:关系型数据库&a…

Mysql数据库基本sql语句

文章目录 1 常用的数据类型2 查看数据库2.1 查看当前服务器中的数据库2.2 查看数据库中的表2.3 查看表的结构(字段) 3 SQL语句3.1 SQL语句分类3.2 创建及删除数据库和表3.2.1 创建新的数据库3.2.2 创建新的表3.2.3 删除指定的数据表3.2.4 删除指定的数据…

数据库的基本语句

这里写目录标题 mysql基本语句表的定义SQL语句分类 mysql基本语句 1.进入数据库 mysql -uroot -p 2.退出数据库 exit; 3.查看有哪些数据库 show databases; 4.创建新的数据库 create database 英文名字; 5.使用数据库 use 名字 6.查看表 show tables; 7.查看数据库版本 select…

Mysql基本语句

1、数据库的基本类型 1.关系数据库 特点:以表和表的关联构成的数据结构 优点:能表达复杂的数据关系。强大的查询语言,能精确查找想要的数据 缺点:读写性能比较差,尤其是海量数据的读写。数据结构比较死板 用途&am…

数据库常用语句汇总

数据库常用语句 一、对数据库的操作1. 用SQL语句创建数据库2.用SQL语句修改数据库3.用SQL语句删除数据库 二、对数据表的操作1.创建数据表定义数据表的约束NULL/NOT NULLUNIQUE约束(唯一约束)PRIMARY KET 主键约束外键约束CHECK 约束 2.修改数据表3.删除…

Mysql数据库基础语句

目录 一,常用的数据类型 二,数据库管理 1,Mysql基础语句操作 1.1,更改密码,登录数据库 1,2查看数据库结构 1.3:查看表结构 2, DDL语句:定义数据中的操作 2.1 创建数据库和表—create 2.2删…

MySql数据库基本语句

绝大多数都是记忆练习的东西 理解的偏少 一,DML语句之Delete(删除) 格式:delete from 表单名; 其后可以增加where条件限制,order by排序,limit限制 例如:delete from employee where name=‘ls’; 删除表中名字为李四的记录。 还有一个删除是truncate 格式:Truncate …

数据库基本语句

用DOS命令窗口操作数据库 启动数据库:mysql -u用户名 -p密码 当出现这个窗口意味着启动成功! DDL语句 数据库操作语句 查询当前所有数据库名称:show databases;创建数据库:create database 数据库名;如…

数据库常用操作语句总结

数据库常用操作语句总结 一、基础1.select 语句2.select distinct 语句3.where 子句4.and 和 or 运算符5.order by 语句6.insert into 语句7.update 语句8.delete 语句 二、高级1.top 子句2.like 操作符3.SQL 通配符4.in 操作符5.between 操作符6.as 别名7.join 表关联8.sql un…

java 数据结构--堆

1.堆的基本定义 二叉树的两种结构,一种是链式结构,一种就是顺序结构,普通二叉树其实是不适合用数组来存储数据,因为会造成大量空间的浪费,但完全二叉树似乎更合适于顺序结构存储,我们通常把堆(完全二叉树) 使用顺序数组来存储。 堆: (1&…

Java实现堆(最大堆)

1、什么是堆 现在有这么一个需求,设计一个结构,满足两个操作要求: 删除时,返回该结构的最大值或者最小值的元素往结构中新增元素 问题:如何组织优先这种结构? 一般数组、链表?有序数组或者链…

五、Java堆

Java堆 对于Java应用程序来说,Java堆 是虚拟机所管理的内存中最大的一块。堆,是被所有线程共享的一块内存区域,在虚拟机启动时创建。堆,唯一的目的就是存放对象实例,Java世界中“几乎”所有的对象实例都是在这里分配内…

java中的堆实现

java中的堆实现 完全二叉树:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。即除了最后一层,其他层的节点个数都是满的,而且最后一层的叶子节点必须靠左。 如图: 二叉堆:必须是完…

[JAVA数据结构]堆

目录 1.堆的概念 2.堆的创建 3.堆的插入与删除 3.1堆的插入 3.2堆的删除 1.堆的概念 如果有一个关键码的集合K {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,…

堆的Java实现

一.概念 堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。 堆的特性: 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是…

堆的创建---java

目录 1、堆的概念 2.堆的存储方式 3、堆的创建 3.1、堆向下调整 3.2、堆的创建 1、堆的概念 如果有一个关键码的集合K {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中&…

【堆】堆的实现(Java)

今天学习堆这种数据结构。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质: 堆中某个结点的值总是不大于或不小于其父结点的值;堆总是一棵完全二叉树。 将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆…

Java之堆和堆排序

目录 一.什么是堆 1.基本介绍 2.堆的实现方式 二.最大堆的实现 1.最大堆 2.思路分析 0.基础操作 1.添加上浮操作 2.删除下沉操作 3.将数组堆化操作 2.代码实现 三.堆排序 1.什么是堆排序 2.思路分析 3.代码实现 一.什么是堆 1.基本介绍 堆是一种数据结构&#…