性能测试项目实战

学习目标

  • 完成性能实战

1. 轻商城项目介绍

① 背景

轻商城项目是一个现在流行的电商项目。我们需要综合评估该项目中各个接口的性能,并给出优化建议,以满足公司未来的发展需要。

② 简介

  • 轻商城是一个支持web和微信小程序的前后端分离架构的项目
  • 前端使用VUE技术框架开发,即支持微信小程序,也支持手机移动端,还支持web页面
  • 后端使用了SpringBoot框架进行开发,MySQL做数据库
  • 目前还在开发完善阶段

③ 项目功能架构

轻商城前台功能:

  • 首页
  • 专题列表、专题详情
  • 分类列表、分类详情
  • 品牌列表、品牌详情
  • 新品首发、人气推荐
  • 优惠券列表、优惠券选择
  • 团购(功能开发中)
  • 搜索
  • 商品详情、商品评价、商品分享
  • 购物车
  • 下单
  • 订单列表、订单详情、订单售后
  • 地址、收藏、足迹、意见反馈
  • 客服

轻商城后台管理功能:

  • 会员管理
  • 商城管理
  • 商品管理
  • 推广管理
  • 系统管理
  • 配置管理
  • 统计报表

④ 项目技术架构

使用的技术栈:

  • Spring Boot
  • Vue
  • 微信小程序

技术架构图:


⑤ 熟悉数据库设计

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






2. 性能测试流程回顾:

  1. 性能测试需求分析
  2. 性能测试计划
  3. 性能测试用例编写
  4. 性能测试脚本编写
  5. 性能测试环境搭建
  6. 执行性能测试脚本
  7. 性能测试监控
  8. 性能分析和调优
  9. 性能测试报告编写

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. 性能测试监控

① 在脚本运行时,需要监控各项资源的占用

  1. 系统指标:系统指标则与用户场景及需求直接相关
    • 并发用户数:某一物理时刻同时向系统提交请求的用户数
    • 平均响应时间:系统处理事务的响应时间的平均值。对于系统快速响应类页面,一般响应时间为3秒左右
    • 吞吐量
  2. 服务器资源指标:资源指标与硬件资源消耗直接相关
    • CPU使用率:一般可接受上限为85%
    • 内存利用率:一般可接受上限为85%
    • 磁盘I/O
    • 网络带宽
  3. Java应用:
    • JVM监控:JVM内存、Full GC频率
  4. 数据库:
    • 慢查询
    • 缓存命中率
    • 数据池连接数
    • mysql锁
  5. 压测机资源:(时刻监视本机的资源占用,避免本机宕机导致压测无效)
    • CPU
    • 内存
    • 网络
    • 磁盘空间

② 性能监控工具

要对性能测试指标进行监控,可以使用系统自带的监控工具,也可以使用第三方监控工具或者监控平台

  1. 系统指标
    • 通过性能测试工具(如LoadRunner、JMeter等)以图形化方式监控
  2. 服务器资源指标
    • 使用Jmeter性能监控插件PerfMon 进行监控
    • 使用Linux命令监控:top、free、vmstat、sar、iostat等
    • Nmon:全面监控linux系统资源使用情况,包括CPU、内存、I/O等,可独立于应用监控
  3. Java应用
    • jvisualvm
  4. 数据库
    • SQL查询
  5. 压测机资源
    • 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压不上去,导致这种现象的原因很多,作为测试人员应配合开发人员进行分析尽快找出瓶颈的所在

常见性能瓶颈分析:

  1. 服务器资源分析
    • CPU瓶颈分析:CPU已压满(接近100%),需要再看其他指标的拐点出现的时刻是否与CPU压满的时刻基本一致
    • 内存瓶颈分析:内存不足时,操作系统会使用虚拟内存,从虚拟内存读取数据,影响处理速度
    • 磁盘I/O瓶颈分析:磁盘I/O成为瓶颈时,会出现磁盘I/O繁忙,导致交易执行时在I/O处等待
    • 网络带宽:如果接口传递的数据包过大,超过了带宽的传输能力,就会造成网络资源竞争,导致TPS上不去
  2. JVM瓶颈分析
    • 分析JVM的内存
  3. 数据库瓶颈分析
    • 慢查询
    • 数据库的连接池设置太小,导致数据库连接出现排队
    • 数据库出现死锁
  4. 程序内部实现机制
  5. 压测机
    • JMeter单机负载能力有限,如果需要模拟的用户请求数超过其负载极限,也会导致TPS压不上去

② 性能调优

性能调优的步骤:

  1. 确定问题:根据性能监控的数据和性能分析的结果,确定性能存在的问题(要求)
  2. 确定原因:确定了问题之后,对问题进行分析,找出问题的原因
  3. 确定调整目标和解决方案(改服务器参数配置/增加硬件资源配置/修改代码)
  4. 测试解决方案
  5. 分析调优结果
注意:性能测试调优并不是一次完成的过程,针对同一个性能问题,上面的五步可能要经过多次循环才能最终完成性能调优的目标(即:测试发现问题-找原因-调整-验证-分析-再测试。。。)

③ 性能调优案例

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. 编写测试报告

编写测试报告的要点

  • 结构清晰
  • 描述简洁
  • 图文混合
  • 数据对比