性能测试项目实战
学习目标
- 完成性能实战
1. 轻商城项目介绍
① 背景
轻商城项目是一个现在流行的电商项目。我们需要综合评估该项目中各个接口的性能,并给出优化建议,以满足公司未来的发展需要。
② 简介
- 轻商城是一个支持web和微信小程序的前后端分离架构的项目
- 前端使用VUE技术框架开发,即支持微信小程序,也支持手机移动端,还支持web页面
- 后端使用了SpringBoot框架进行开发,MySQL做数据库
- 目前还在开发完善阶段
③ 项目功能架构
轻商城前台功能:
- 首页
- 专题列表、专题详情
- 分类列表、分类详情
- 品牌列表、品牌详情
- 新品首发、人气推荐
- 优惠券列表、优惠券选择
- 团购(功能开发中)
- 搜索
- 商品详情、商品评价、商品分享
- 购物车
- 下单
- 订单列表、订单详情、订单售后
- 地址、收藏、足迹、意见反馈
- 客服
轻商城后台管理功能:
- 会员管理
- 商城管理
- 商品管理
- 推广管理
- 系统管理
- 配置管理
- 统计报表
④ 项目技术架构
使用的技术栈:
- Spring Boot
- Vue
- 微信小程序
技术架构图:

⑤ 熟悉数据库设计
- 熟悉数据库设计结构,便于后期对数据库的性能监控
- 性能测试的过程中,数据库容易出现性能瓶颈






2. 性能测试流程回顾:
- 性能测试需求分析
- 性能测试计划
- 性能测试用例编写
- 性能测试脚本编写
- 性能测试环境搭建
- 执行性能测试脚本
- 性能测试监控
- 性能分析和调优
- 性能测试报告编写
3. 性能测试需求分析
① 性能测试点的提取规则
- 用户频繁使用的业务功能
- 非常关键的业务功能
- 特殊交易日或峰值交易的业务功能
- 核心业务发生重大调整的业务功能
- 资源占用非常高的业务功能
轻商城的测试点提取如下:

② 确定性能测试目标
轻商城作为一个新开发的项目,性能测试目标包括:
- 确定核心业务功能的TPS
- 对业务流程(多接口组合)进行压测
- 系统能在实际系统运行压力的情况下,稳定的运行24小时
期望的TPS和最大响应时间:

4. 性能测试计划编写

5. 性能测试用例编写

6. 性能测试脚本编写
① 调试脚本:目的是确保每个接口的脚本都能正常运行

- HTTP信息头管理器:提取出公共信息头“Content-Type:application/json;charset=utf-8”
- HTTP请求默认值:将[协议]、[域名]、[端口号]、[内容编码]进行提取
- 响应断言:[响应代码]等于[200]
- JSON断言:[响应报文中的某个提示语]等于[指定的值]
② 优化脚本:将需要参数化的、上下接口关联的进行调优
- [登录]接口的“username”需要参数化
- [搜索]接口的“keyword”需要参数化
- [查看商品详情]接口的“id”需要参数化,与[加入购物车]的“goodsId”一致
- [加入购物车]接口的“goodsId”、“productId”需要相互匹配,并参数化
- [勾选商品]接口的“productIds”需要和[加入购物车]的商品“productId”匹配
- [提交订单]接口的“addressId”需要和当前用户匹配
7. 性能测试环境搭建
① 性能测试环境的特点
- 性能测试对测试环境的独立性要求更高,更为严格,不能有其他干扰动作混杂其中
- 尽量保持性能测试环境与真实生产环境的一致性
② 如何保证测试环境与生产环境的一致性
- 硬件环境
- 包括服务器环境、网络环境等
- 软件环境
- 版本一致性:包括操作系统、数据库、被测应用程序、第三方软件等
- 配置一致性:包括操作系统、数据库、被测应用程序、第三方软件等
- 使用场景的一致性
- 基础业务数据的一致性
- 业务操作模式的一致性:尽量模拟真实场景下用户的使用情况
③ 构造测试数据
为什么要添加测试数据?
- 更贴近千千万万个用户真实使用被测系统的情况
- 避免触发缓存机制,导致压力没有真正传达到各个模块
压测环境中的数据量尽量与生产环境中数据量一致,为了快速创建大量数据,可以直接操作数据库进行添加,但前提是你一定要非常了解项目的数据库表结构,建议且不限于以下方法:
- 通过编写Python脚本,跑SQL造数据
- 通过jmeter脚本,跑SQL造数据
- 通过直接运行SQL语句造数据
案例1:通过编写python,造一万条轻商城的商品数据:
import pymysql
conn = pymysql.connect(host="192.168.0.192", user="root", password="123456", database="litemall", port=3306, charset="utf8")
cursor = conn.cursor()
goods_sql = """ INSERT INTO `litemall_goods` (`id`, `goods_sn`, `name`, `category_id`, `brand_id`, `gallery`, `keywords`,
`brief`, `is_on_sale`, `sort_order`, `pic_url`, `share_url`, `is_new`, `is_hot`, `unit`, `counter_price`, `retail_price`,
`detail`, `add_time`, `update_time`, `deleted`)
VALUES ('{}', '{}', '小米手机-{}', '1008016', '1001000', '[\"http://182.92.81.159:8080/wx/storage/fetch/0b5lpf15ee8c6o10vkd8.jpg\",
\"http://182.92.81.159:8080/wx/storage/fetch/ykaptr4vntbofoi9l2f5.jpg\",\"http://182.92.81.159:8080/wx/storage/fetch/u5zc8sp2t4f9y9uw n179.jpg\"]',
'手机,Android', '小米10 双模5G 骁龙865 1亿像素8K电影相机', '1', '100', 'http://182.92.81.159:8080/wx/storage/fetch/re5jul69plklzusso97 t.png',
'', '1', '0', '个', '100.00', '99.00', '<p>小米10 双模5G 骁龙865 1亿像素8K电影相机 对称式立体声 8GB+256GB 冰海蓝 拍照智能游戏手机 !!!!!!!!!!</p>',
'2020-03-11 14:19:50', '2020-03-26 14:55:58', '0'); """
goods_attr_sql = """ INSERT INTO `litemall_goods_attribute` (`goods_id`, `attribute`, `value`, `add_time`, `update_time`, `deleted`)
VALUES ('{}', '产地', '中国山东', '2018-10-26 21:27:13', '2018-10-26 21:27:13', '0'), ('{}', '尺寸', '200*230cm/ 220*240cm',
'2018-10-26 21:27:13', '2018-10-26 21:27:13', '0'), ('{}', '颜色', '条纹绿格', '2018-10-26 21:27:13', '2018-10-26 21:27:13', '0'),
('{}', '执行标准', 'GB/T 22844-2009', '2018-10-26 21:27:13', '2018-10-26 21:27:13', '0'); """
goods_product_sql = """ INSERT INTO `litemall_goods_product` (`goods_id`, `specifications`, `price`, `number`, `url`, `add_time`, `update_time`, `deleted`)
VALUES ('{}', '[\"标准\"]', '999.00', '1000', 'http://182.92.81.159:8080/wx/storage/fetch/o4531ja3h9sgq5ib32f4.jpg', '2020-03-11 14:19:50', '2020-03-11 14:19:50', '0'); """
goods_spec_sql = """ INSERT INTO `litemall_goods_specification` (`goods_id`, `specification`, `value`, `pic_url`, `add_time`, `update_time`, `deleted`)
VALUES ('{}', '规格', '标准', '', '2020-03-11 14:19:50', '2020-03-11 14:19:50', '0'); """
goods_id_start = 1200002
for i in range(2):
# 商品
goods_id = goods_id_start + i
print("i={} goods_id={}".format(i, goods_id))
sql = goods_sql.format(goods_id, goods_id, goods_id)
cursor.execute(sql)
# 商品参数
sql = goods_attr_sql.format(goods_id, goods_id, goods_id, goods_id)
cursor.execute(sql)
# 商品货品
sql = goods_product_sql.format(goods_id)
cursor.execute(sql)
# 商品规格表
sql = goods_spec_sql.format(goods_id)
cursor.execute(sql)
conn.commit()
cursor.close()
conn.close()
案例2:通过编写jmeter脚本,造一万条轻商城的用户数据:








案例3:通过编写SQL存储函数,给刚刚新增的用户添加对应的收货地址:
DROP PROCEDURE if exists add_address; -- 删除存储函数
DROP PROCEDURE if exists selectUid; -- 删除存储函数
delimiter $$ -- 分隔符
CREATE PROCEDURE add_address(in uid int)
BEGIN
if uid != 0 then
INSERT INTO `litemall`.`litemall_address` (`id`, `name`, `user_id`, `province`, `city`, `county`, `address_detail`, `area_code`, `tel`, `is_default`, `add_time`, `update_time`)
VALUES (uid, '苏芙蓉', uid, '河北省', '石家庄市', '长安区', '东门101号', '130102', '13282135001', 1, '2022-05-05 18:32:30', '2022-05-05 18:32:34');
ELSE
SELECT uid;
END if;
END $$
delimiter; -- 分隔符
delimiter $$ -- 分隔符
CREATE PROCEDURE selectUid()
BEGIN
declare i int;
declare uid int;
set i = 4;
WHILE i<=100159 DO
set uid = (SELECT u.id FROM litemall_user u WHERE u.id = i);
set uid = IFNULL(uid,0);
CALL add_address(uid);
set i = i+1;
END WHILE;
END $$
delimiter; -- 分隔符
CALL selectUid;
8. 执行性能测试脚本
- 将服务器的ServerAgent监听服务启动
- 一个一个执行接口脚本,每执行完一个,保存一个数据(可截图、可导出csv)
- 每执行一个脚本,需要间隔小段时间,让服务器恢复常态
- 必要时候可以加入分布式压测
9. 性能测试监控
① 在脚本运行时,需要监控各项资源的占用
- 系统指标:系统指标则与用户场景及需求直接相关
- 并发用户数:某一物理时刻同时向系统提交请求的用户数
- 平均响应时间:系统处理事务的响应时间的平均值。对于系统快速响应类页面,一般响应时间为3秒左右
- 吞吐量
- 服务器资源指标:资源指标与硬件资源消耗直接相关
- CPU使用率:一般可接受上限为85%
- 内存利用率:一般可接受上限为85%
- 磁盘I/O
- 网络带宽
- Java应用:
- JVM监控:JVM内存、Full GC频率
- 数据库:
- 慢查询
- 缓存命中率
- 数据池连接数
- mysql锁
- 压测机资源:(时刻监视本机的资源占用,避免本机宕机导致压测无效)
- CPU
- 内存
- 网络
- 磁盘空间
② 性能监控工具
要对性能测试指标进行监控,可以使用系统自带的监控工具,也可以使用第三方监控工具或者监控平台
- 系统指标
- 通过性能测试工具(如LoadRunner、JMeter等)以图形化方式监控
- 服务器资源指标
- 使用Jmeter性能监控插件PerfMon 进行监控
- 使用Linux命令监控:top、free、vmstat、sar、iostat等
- Nmon:全面监控linux系统资源使用情况,包括CPU、内存、I/O等,可独立于应用监控
- Java应用
- jvisualvm
- 数据库
- SQL查询
- 压测机资源
- Windows自带“任务管理器”
③ Mysql监控
Mysql常用监控指标:
- 慢查询SQL
- 慢查询:指执行速度低于设置的阀值的SQL语句
- 作用:帮助定位查询速度较慢的SQL语句,方便更好的优化数据库系统的性能
开启MySQL慢查询日志:
参数说明:
- slow_query_log: 慢查询日志开启状态[ON:开启,OFF:关闭]
- slow_query_log_file: 慢查询日志存放位置
- long_query_time: 慢查询时长设置(超过该时长才会被记录,单位:秒)
设置步骤:
1. 查询相关参数配置
mysql> show variables like 'slow_query%';
+---------------------+-------------------------------------------------------------+
| Variable_name | Value
|
+---------------------+-------------------------------------------------------------+
| slow_query_log | OFF
|
| slow_query_log_file | /var/lib/mysql/iZ2ze6id2tww1o2zn0mznxZ-slow.log |
+---------------------+-------------------------------------------------------------+
2 rows in set
mysql> show variables like 'long_query_time';
+-------------------+-----------+
| Variable_name | Value |
+-------------------+-----------+
| long_query_time | 10.000000 |
+-------------------+-----------+
1 row in set
2. 开启慢查询并配置
# 开启慢查询日志
mysql> set global slow_query_log='ON';
# 设置慢查询日志存放位置
mysql> set global slow_query_log_file='/data/slow_query.log';
# 设置慢查询时间标准,设置之后会在下次会话才生效
mysql> set global long_query_time=1;
④ JVM监控
使用本地jvisualvm远程监控服务器
I. 添加轻商城start.sh启动包的参数,并重启服务
- -Dcom.sun.management.jmxremote
- -Djava.rmi.server.hostname=182.92.81.159
- -Dcom.sun.management.jmxremote.port=10086
- -Dcom.sun.management.jmxremote.ssl=false
- -Dcom.sun.management.jmxremote.authenticate=false
II. 进入本地jdk安装目录bin目录,找到jvisualvm.exe并启动
III. 右键“远程”选择“添加远程主机”,并输入主机IP

IV. 右键主机选择“添加JMX连接”,并输入JMX端口

V. 连接成功后在主机下会有对应的连接显示,双击查看监控信息

10. 性能分析和调优
① 性能测试瓶颈分析
在实际的性能测试中,会遇到各种各样的问题,比如TPS压不上去,导致这种现象的原因很多,作为测试人员应配合开发人员进行分析尽快找出瓶颈的所在
常见性能瓶颈分析:
- 服务器资源分析
- CPU瓶颈分析:CPU已压满(接近100%),需要再看其他指标的拐点出现的时刻是否与CPU压满的时刻基本一致
- 内存瓶颈分析:内存不足时,操作系统会使用虚拟内存,从虚拟内存读取数据,影响处理速度
- 磁盘I/O瓶颈分析:磁盘I/O成为瓶颈时,会出现磁盘I/O繁忙,导致交易执行时在I/O处等待
- 网络带宽:如果接口传递的数据包过大,超过了带宽的传输能力,就会造成网络资源竞争,导致TPS上不去
- JVM瓶颈分析
- 分析JVM的内存
- 数据库瓶颈分析
- 慢查询
- 数据库的连接池设置太小,导致数据库连接出现排队
- 数据库出现死锁
- 程序内部实现机制
- 压测机
- JMeter单机负载能力有限,如果需要模拟的用户请求数超过其负载极限,也会导致TPS压不上去
② 性能调优
性能调优的步骤:
- 确定问题:根据性能监控的数据和性能分析的结果,确定性能存在的问题(要求)
- 确定原因:确定了问题之后,对问题进行分析,找出问题的原因
- 确定调整目标和解决方案(改服务器参数配置/增加硬件资源配置/修改代码)
- 测试解决方案
- 分析调优结果
③ 性能调优案例
I. 获取首页数据
场景描述:进入首页后,加载首页的相关数据,包括:轮播图、频道、优惠券、团购专区、品牌商直供、新品首发、热卖商品、专题精选等数据
测试结果数据:

上面这个图能看出什么问题呢?
问题一:CUP都已经接近100%!
问题二:一次请求中需要查询很多数据
解决方案:
方案一: 提升服务器配置
方案二:分批次、异步加载首页数据,首页底部的数据(如:新品首发、热卖商品、专题精选等数据)等用户向下滑动页面时再加载
II. 查看商品详情
场景描述:进入商品详情页面时,加载商品的详细信息

问题一:网络带宽已跑满
问题二:一次请求中返回了全部数据
解决方案:
方案一:提升服务器网络带宽
方案二:分批次、异步加载商品数据
III. 搜索商品
场景描述:进入首页,在搜索框中输入关键字搜索商品
测试结果数据:
搜索关键字“床”时,出现慢查询SQL语句
查看慢查询语句:
cat /var/lib/mysql/localhost-slow.log
select id, `name`, keywords, `desc`, pid, icon_url, pic_url, `level`, sort_order, add_time, update_time, deleted
from litemall_ category
WHERE ( id in (1008009, 1008009, 1008008, 1008008, 1015000, 1015000, 1008009, 1008009, 1008009, 1008008, 1036000) and `level` = 'L2'
and deleted = 0);
问题分析:
找出搜索商品接口对应的SQL语句(通过查看代码实现或者从日志中获取查询SQL)

将具体数据代入SQL的占位符:

分析问题:
- 当搜索关键字匹配到大量的商品时,第3条SQL语句会返回大量重复数据
- 第4条SQL语句中的in查询条件中同样包含大量重复的商品分类id
- 第3条和第4条SQL都会出现查询时间较长
解决方案:
- 有大量数据重复时,需要进行去重处理
- 协同DBA共同完成SQL优化
IV. 模拟JVM内存溢出
场景描述:请求测试接口(/wx/index/oom),模拟内存溢出
测试结果数据:


存在问题:JVM内存占用随着时间的推移占用越来越多,直至内存溢出,系统停摆退出
解决方案:程序员排查代码存在的问题,及时释放无用的对象
11. 编写测试报告
编写测试报告的要点
- 结构清晰
- 描述简洁
- 图文混合
- 数据对比
