使用 JMeter 进行复杂场景下的 压力测试

背景: 之前介绍了轻量级 http 压测工具 wrk 的使用。wrk 面对单一的 http 请求非常方便, 但如果面对复杂场景下下的压测需要则显得捉襟见肘,此时就需要更为强大的压测工具Apache JMeter 的登场。 此贴致力于让你最快速的上手 JMeter,了解如何用 Jmeter 实现一些复杂场景下的压测,比如需要压测grpc接口, 级联多个请求, 多个地址的负载均衡, 统计和绘制响应时长 。 希望你有 grpc 的使用经验

准备被压测的 http 和 grpc 接口

新建工程

创建新目录 jmeter_demo 在该目录下打开命令行输入:

shell 复制代码
go mod init jmeter_demo
go mod tidy

随后创建各目录与文件如下:

shell 复制代码
-- jmeter_demo

  -- main main 函数目录(启动http和grpc服务)
    -- main.go 
    
  -- proto grpc server 的目录
    -- chat.go
    -- chat.pb.gp
    -- chat.proto

  -- test_plan jmeter 测试计划
    -- test_plan.jmx
  
  go.mod

main 函数

go 复制代码
 
package main

import (
   "google.golang.org/grpc"
   "io"
   proto "jmeter_demo/proto"
   "log"
   "net"
   "net/http"
)

func main() {

   // start a grpc service
   lis, err := net.Listen("tcp", ":8000")
   if err != nil {
      log.Fatalf("Fail to listen: %v", err)
   }

   s := proto.Server{}

   grpcServer := grpc.NewServer()

   proto.RegisterChatServiceServer(grpcServer, &s)

   go func() {
      if err := grpcServer.Serve(lis); err != nil {
         log.Fatalf("Fail to serve: %v", err)
      }
   }()

   // start a http service
   http.HandleFunc("/fib1", fibHandler1)
   http.HandleFunc("/fib2", fibHandler2)
   go func() {
      http.ListenAndServe(":2000", nil)
   }()

   select {}

}

func fibHandler1(w http.ResponseWriter, r *http.Request) {
   if r.Method != http.MethodPost {
      w.Write([]byte("method error"))
      return
   }
   bodyBytes, err := io.ReadAll(r.Body)
   if err != nil {
      w.Write([]byte("read body error"))
      return
   }
   if string(bodyBytes) == "" {
      w.Write([]byte("body empty"))
      return
   }

   fib(0)
   w.Write([]byte("hello from fib handler1"))
}

func fibHandler2(w http.ResponseWriter, r *http.Request) {
   if r.Method != http.MethodPost {
      w.Write([]byte("method error"))
      return
   }
   bodyBytes, err := io.ReadAll(r.Body)
   if err != nil {
      w.Write([]byte("read body error"))
      return
   }
   if string(bodyBytes) == "" {
      w.Write([]byte("body empty"))
      return
   }

   fib(30)
   w.Write([]byte("hello from fib handler2"))
}

func fib(n int) int {
   if n < 2 {
      return 1
   }
   return fib(n-1) + fib(n-2)
}

main 函数非常的 简单, 起了一个 grpc 接口 和两个 http 接口, 两个http 接口功能完全一致,都是运行一个斐波那契数列计算后返回一个字符串, 但两个 handler 的返回略有不同。

grpc 服务的函数非常简单, 直接 return 了一个字符串, 我展示出来, 就不做详细解释了, 具体开源参照我的另一篇博客

chat.proto

proto 复制代码
syntax = "proto3";

package proto;

option go_package="/";

// 定义 message
message ChatMessage {
  string body = 1;
}

// 定义 service
service ChatService {
  rpc SayHello(ChatMessage ) returns (ChatMessage ) {}
}

chat.go

go 复制代码
package __

import (
   "fmt"
   "golang.org/x/net/context"
)

type Server struct {
}

func (s *Server) SayHello(ctx context.Context, in *ChatMessage) (*ChatMessage, error) {
   fmt.Printf("Receive message body from client: %s \n", in.Body)
   return &ChatMessage{Body: "Hello From the Grpc Server!"}, nil
}

Jmeter 的使用

Jmeter 环境搭建

  1. Jmeter 运行依赖于的 java 环境, 所以需要你提前安装好 java 环境。安装好 Java 后大家可以前往官网下载Jmeter. 这篇博客的Jmeter 版本是 Jmeter 5.6.2 版本。安装完以后,点击安装目录/bin/jmeter.bat 即可启动jmeter.

  2. 安装Jmeter 插件。JMeter 默认是不支持 grpc的,需要额外安装插件, 大家可以前往JMeter的github, 下载 jmeter-grpc-request.jar 包然后放入 jmeter安装目录/lib/ext下。

  3. 安装绘制响应时间图插件。大家前往 Jmeter插件网站, 下载 plugin-manager.jar 包, 然后也是放入 jmeter安装目录/lib/ext下.

安装完插件以后, 你需要重启 JMeter

JMeter 重要概念

下面这张图是这篇博客使用到的 JMeter 的完整展示, 我将围绕着下图中的各个组件对JMeter如何使用进行解释。

测试计划 (Test Plan):

测试计划,是 Jmter 中的工作单位,Jmeter 的测试都是以测试疾患为单位的,测试计划可以被保存,也可以被导入。 在测试计划中可以 自定义一些变量方便后续使用。 比如我就定义了两个变量 fib1 和 fib2. 一打开Jmeter 就会有一个 Test plan 的测试计划放在左上角了

线程组(Thread Group)

线程组,相当于测试计划的流水线,压测过程严格按照线程组的规定进行。比如我们可以设置发起压测请求所调用的用户线程数, 压测的次数或者 压测的时间, 遇到报错如何处置(暂停测试还是进行下一次测试)等等。

我们在测试计划右键 -> Add -> Thread(Users) -> Thread Group 即可添加线程组

事务控制(Transaction Controller)

Jmeter 的事务和 MYSQL中的 事务类似,一个事务中可以包括多个请求, 比如 我在这篇博客中有两个请求,一次HTTP请求, 一次GRPC 请求, 只有两个请求都正常返回, 这个事务才算正常完成。

我们在线程组 -> Add -> Logic Controller -> Transaction ontroller 即可添加 事务控制

Sampler 采样器

所谓采样器就是,就是对接口进行采样, 也就是发起请求的控制器。

http 采样器

比如下图的中的 http 采样器 可以通过在 线程组, 右键 -> sampler -> http sampler 添加

我们可以定义http方法, 请求URL,和参数进行配置。 特别的是, 我在请求地址这里用到了一个随机函数, 可以对请求的地址进行随机化处理, 也就是 对 fib1 和 fib2 进行 1:1 的负载均衡。 如果想要实现其他比例的负载均衡, 那么可以在 测试计划设置自定义 变量的数量和比例来间接实现。

grpc 采样器

grpc 采样器 可以通过在 线程组, 右键 -> sampler -> grpc sampler 添加

grpc 需要指定对应的 .proto 文件路径, 如果正确添加了路径,那么在 Full Method 的下拉框下面是可以看到函数的可选项的。proto 的字段可以通过 json 的形式进行添加。

响应断言(Response Assertion)

所谓响应断言, 就是判断响应是否正常的判断条件。比如下图中的判断条件是 响应是否包含特定的字符来判断。可以在 sampler 下右键 -> listener -> Response Assertion 添加

查询结果树(View Result Tree)

查询结果树就是查看响应的详细情况, 比如查询 http 返回的body, header。 可以在 sampler 下右键 -> listener -> View Result Tree 添加

聚合报告(Summary Report)

在聚合报告中, 可以直接输出整个事务控制下的性能指标, 包括请求次数, 响应时长, 吞吐量,传输数据量等关键指标。 可以在事务控制器下 右键 -> listener -> Summary Report 添加

响应时间图(Response Time Graph)

响应时间图可以绘制响应时间于时间的关系图,可以自定义地设置绘图采样的时间区间长度, 图片尺寸等。可以在事务控制器下 右键 -> listener -> Response Time Graph 添加。

点击 Display Graph 即可绘制响应时间图:

启动测试

点击下图中右边的扫把即可清理掉历史数据, 点击左边的三角形即可开始测试

最后输出 响应时长图:

可以看到整个事务下的多个请求的响应时间,hello grpc 接口只是一个字符串返回,所以耗时非常短;fib http 接口由于需要计算斐波那契数列, 所以耗时更长。

踩坑经验

JMeter 本身是一个比较成熟的压测工具, 并无特别大的坑。 我唯一遇到的坑是页面配置的问题。启动 JMeter 后会开启一个命令行, 如果遇到无法保存测试计划, 无法启动测试等情况, 且命令行报以下类似的错误:

shell 复制代码
Uncaught Exception java.lang.NoClassDefFoundError: 
Could not initialize classorg.apache.jmeter.gui.FileDialoger in thread Thread[AWT-EventQueue-0,6,main]. 
See log file for details.

你可以尝试以下更换语言(换成英语)或者更换页面主题,点击左上角的 Options -> Look and Feel.

相关推荐
Amagi.1 分钟前
Spring中Bean的作用域
java·后端·spring
2402_8575893624 分钟前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊32 分钟前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso36 分钟前
Rust 快速入门(一)
开发语言·后端·rust
sco528236 分钟前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子1 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码1 小时前
详解JVM类加载机制
后端
努力的布布1 小时前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU2 小时前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛2 小时前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala