高性能问题
内容管理
Web性能测试 解决方案: JMeter 【不是代码单元,而是场景】
依靠JMH可以完成基本的性能测试,进行代码优化,比如在JVM预热情况下,使用Lambda、Stream比增强for 效率高;
在BS架构流行的当下,需要对场景进行性能测试,比如登录场景,商品购买,支付场景等: 常见的问题:
- 秒杀系统最多可以抗住多大的流量不出现异常?
- 连续100小时以上的疲劳测试是否会让系统内存无法下降,GC无法回收内存,CPU不降低?
- 高并发压力下,应用程序哪里消耗资源最多,需要优化?
- 针对协议的测试: 比如之前的聊天服务,HTTP轮询 和 WebSocket谁更消耗性能?相差多少?
- 单台压力机无法生成更大压力,如何增加压力机?
JMeter web性能测试
上面的问题针对的都是web应用,也就是会进行浏览器的访问,都可以依靠性能测试工具—JMeter; 桌面GUI可以允许用户无代码编写脚本,通过后台启动JMeter的方式允许脚本,测试的是场景,而不是JMH的代码单元; (单元 — 集成);
JMeter项目整体测试,JMH代码局部测试优化
- 下载 安装
直接到官网下载压缩包即可: Apache JMeter - Download Apache JMeter ,Cfeng的JDK为10以上版本,选用的JMeter为5.4.3, 5.3版本出现了一些奇怪的问题
下载之后解压安装, 进入bin目录,点击jmeter.sh就可以允许cmd窗口打开GUI程序,cmd窗口不能关闭
这里可以直接在查看高级系统变量位置将环境变量配置上 ---- 也就是安装目录的bin配置到path中即可,这样可以直接在cmd窗口输入jmeter就可以启动
JMeter可以对多种协议进行测试: 比如常用的http、https、ws、wss、tcp、udp、ftp等以及对NoSQL、MySQL、JMS等数据源进行性能测试
可以直接编写性能测试脚本,也可以通过脚本录制的方式进行性能测试 ---- 网页代理进行页面上的操作,操作流程被录制生成性能测试脚本
JMeter配置原件
Jmeter完成压力测试需要借助各种配置原件,完成一个最简单的抢红包测试,需要借助Http请求的Sampler、查看结果树的Listener、CSV配置辅助的 config Element
取样器 Sampler
基础单元, 各种协议的请求都是由取样器发起的。 比如HTTP请求、FTP请求、JMS发布订阅、Java请求、JDBC请求、TCB请求等、SMTP请求
当然最常用的还是HTTP Request,在其中配置相关的host、port、contextPath、encoding等信息 以及配置相关的请求参数, 请求参数可以使用CSV文件进行指定变量; ${XXX}即可引入文件的数据
配置原件 config Element
主要对上面取样器的各种请求进行辅助配置
配合取样器的HTTP信息头管理器 Http Header Manager、缓存管理器 Http cache manager、 Cookie管理器、JDBC connection Configuration 连接配置、 LDAP Extended Request Defaults LDAP扩展请求,…
逻辑控制器 Logic Controller
主要控制JMeter脚本的执行顺序, 比如从接口请求得到结果,经过逻辑Controller判断之后,执行接口2等, 灵活度更高,包括if控制器、 事务、循环、While、ForEach、仅一次、吞吐量、Switch等
前置处理器 Pre-Processor
执行取样器前,执行前置处理器,最简单的就是执行Http访问请求前需要 执行的前置处理, 比如HTML链接处理器、HTML URL重写修饰符和取样器超时等
后置处理器 Post-Processor
执行取样器后,执行后置处理器,后置处理器包括CSS/JQuery提取器、JSON提取器、边界提取器、BeanShell断言和正则表达式提取器等, 在提取数据后,可以对上个请求的结果参数化,传输到下一个请求中
断言 Assertion
断言可以验证服务器返回的数据和预期是否相符,如果不相符,可以记录或者停止测试计划,【可以类比SpringBoot自动化测试的断言】,包括响应断言、JSON、大小、HTML、XML、BeanShell
定时器 Timer
测试计划执行过程中,定时器减缓线程的运行,eg: 线程在请求接口1执行之后,需要暂停若干ms后请求2,包括固定、随机的定时器
监听器 Listener
监听器主要收集测试结果报告, 主要包括: 查看结果树、断言结果、聚合报告、若有插件: 还可以监听被测试端的CPU和内存
JMeter参数化
Jmeter的参数化实现方式包括函数、外部文件、提取的上一个接口的数据
JMeter提取数据后,在HTTP请求脚本中通过${变量名称} 使用配置的参数,除请求body外,任何地方都可以使用变量、路径、服务器名称或者IP、端口、协议编码
JMeter函数
JMeter函数可以通过函数生成相关的数据供发起HTTP请求
通过这个GUI界面 --》 打开Tools工具 —> 选择Function Helper Dialog 函数助手对话框
在这里面可以选择BeanShell、或者Random随机数字、RandomString随机字符串、Random随机时间、splt字符串拆分、property自定义数值、UUID和char等
如果上面选择RandomString, 拷贝粘贴字符串位置写 ** R a n d o m S t r i n g ( 16 C f e n g 123 ) ∗ ∗ , 在前面的 i n p u t 中输入数据,点击 G e n e r a t e 就可以随机生成,赋值生成的函数,就可以生成,也就是上面的 {_RandomString(16Cfeng123)}**, 在前面的input中输入数据, 点击Generate就可以随机生成,赋值生成的函数,就可以生成, 也就是上面的 RandomString(16Cfeng123)∗∗,在前面的input中输入数据,点击Generate就可以随机生成,赋值生成的函数,就可以生成,也就是上面的{XXX} ---- 引入变量
JMeter读取外部文件
JMeter是可以读取外部的CSV文件的,直接新建一个CSV文件, 在线程组中添加CSV配置原件
使用变量主要的是指定变量,JMeter函数input中生成,而这里的CSV文件,需要在这里配置CSV文件中数据的变量的名称,在HTTP请求脚本中通过${}引用
JMeter提取上个接口数据
在JSON计划中添加JSON提取器,编写JSON提取器的相应规则,在Names of created variables后面配置HTTP请求中编写的变量名称, 在JSON path expressions 中填写JSON的value映射 eg: name --> $.name
JMeter 实战 — 测试高并发抢红包
JMeter是支持选择语言的 ---- Options -》 ChooseLanguage —> Chinese(Simplified),修改语言
首先直接新建一个测试计划,命名之后; 在测试计划下面新建线程组,设置线程数、 Rame-up时间; 这里Cfeng直接设置1s内执行1000个线程 ---- 这样才符合高并发要求
- 线程数: 此次测试总共开启的线程数(一次访问就是一个线程)
- Ramp-up: 表示在多少时间开启所有的线程数,如果设置时间过长,可能导致刚启动请求压力不足,不符合预期; 过短则可能导致压力过大,服务器崩溃,比如设置总现场数的10%
- 循环次数: 整体线程组的循环执行次数, 请求事务总数 = 线程数 * 循环系数
- 调度器: 配合循环次数和永远 同时使用,即不按照循环次数进行执行,而是根据时间循环所有线程,总事务数没有预期
之后就是在线程组下面添加HTTP请求取样器
HTTP取样器的使用和PostMan没有太大的差别; 这里应该是在该请求下添加查看结果树和CSV文件设置
当时Cfegn直接放在线程组下面,因为只是测试一个接口,所以影响不大,配置完成之后,点击运行即可
可以看到这里就成功发起1000次请求,因为Cfeng让这些请求访问的是缓存并且加上了分布式🔒,所以最终结果符合预期,只有10个人抢到了红包
测试的之前的结果,1s发起了1000次请求【来自15个用户左右】,一个用户点击多次,因为秒杀,实际一个用户会点击很多次,加了分布式锁再配合原子命令实现实现类抢红包的操作,可以看到这里能够抗住压力,是因为操作的都是在缓存中,总共的写入数据库的操作一共就10次,并且设置了为异步方式, 不需要每次都访问数据库
1s内所有的红包都抢光了,所以最后日志打印读取total的数据都是0,因为不同的用户的进程都同时进入了Service
2022-09-13 22:50:25.780 WARN 22864 --- [ task-7] c.a.druid.pool.DruidAbstractDataSource : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/db_middleware?useUnicode=true&characterEncoding=utf-8&useSSL=true&servertimezone=GMT%2B8, version : 1.2.8, lastPacketReceivedIdleMillis : 113460
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-33] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10025 key=redis:red:packet:10011:52103535738800 金额=0.64
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-20] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10030 key=redis:red:packet:10011:52103535738800 金额=0.31
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-52] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10026 key=redis:red:packet:10011:52103535738800 金额=0.78
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-57] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10011 key=redis:red:packet:10011:52103535738800 金额=1.07
2022-09-13 22:50:25.783 INFO 22864 --- [nio-8081-exec-7] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10015 key=redis:red:packet:10011:52103535738800 金额=1.9
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-26] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10016 key=redis:red:packet:10011:52103535738800 金额=1.57
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-53] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10013 key=redis:red:packet:10011:52103535738800 金额=1.06
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-56] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10021 key=redis:red:packet:10011:52103535738800 金额=0.51
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-68] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10020 key=redis:red:packet:10011:52103535738800 金额=0.55
2022-09-13 22:50:25.783 INFO 22864 --- [io-8081-exec-65] c.server.service.impl.RedServiceImpl : 当前用户抢到红包了:剩余红包数量:0,userId=10028 key=redis:red:packet:10011:52103535738800 金额=1.61
录制性能测试脚本
通过页面单击的形式,记录所有的请求信息,包括HTTP响应信息和请求头
通常使用JMeter的HTTP代理服务器录制性能测试脚本,【除此之外,还可以使用BadBoy工具仅录制,录制为jmx文件
测试计划 --> 添加 --> 非测试原件 —> HTTP代理服务器
在代理服务器中可以编写自定义的代理端口号,在目标控制器进行配置,目标控制器代表此次录制的脚本放置在哪个线程组中,配置结束,点击启动按钮即可
搭建代理服务器后,配置本地计算机的Internet属性: 增加代理服务器、刚自定义端口号
代理服务器进行请求的接口已经录制到线程组中
最开始问题的advise:
- 编写JMeter脚本,一台JMeter模拟正常用户正使用,另外模拟JMeter登录,模拟登录的JMeter性能提高,得到的JMeter报表就是当前应用程序可承受最大压力
- 测试将JMeter压力开到最大,监控秒杀系统的CPU和内存是否异常,定时检查不同数据源的的数据的一致性(mysql、redis),可得出秒杀系统最大压力
- 服务器不断运行: 查看JVM的GC信息、CPU和内存,确保程序长时间运行,可以将使用的内存正常回收 ---- 检测是否可以长期运行
- 使用Arthas工具、监控当前测试端口,了解线程消耗: 比如使用Redison操作Redis,查看Redisson实际开启了多少个线程,每个线程消耗的CPU和内存,查看哪个接口的响应速度最慢
- 使用JMeter脚本录制方式,通过网络代理仿照正常用户获得JMeter脚本,只需要调整参数即可
- 通过JMeter插件可以完成不同协议测试,使用即可; 当单台JMeter无法增加更大压力,可以部署JMeter集群💙