- A+
1. 前言
通过之前的学习,我们已经掌握了crank的配置以及对应http基准工具bombardier、wrk、wrk2的用法,本篇文章介绍一下如何将其用于实战,在实际的项目中我们如何使用crank来完成压测任务。
2. 项目背景
目前有一个项目,我们希望通过压测来了解其QPS、吞吐量、以及临界值,并通过压测来分析出其瓶颈在哪里?并通过解决瓶颈问题以提高QPS、吞吐量等指标
先看下我们手头掌握了什么:
- 项目信息
- 项目中的接口基本都需要登录
- 通过与开发沟通可以得到每个页面的接口信息以及参数信息
- 环境信息
- 压测项目有单独的环境部署应用、Redis、数据库等基础配置
此处项目名我们暂定为ProjectA。
3. 如何开展
首先我们先回顾一下Agent、Controller的职责以及特点
- Controller
- 做任务调度以及结果输出
- 无需单独服务器,可以在本机执行发送命令,需要与Agent相通
- Agent
- 任务的实际执行者
- 单任务执行,不能做到接收到多个任务并同时执行,先收到哪个任务,哪个任务会先执行
- 相同一个任务可以被多个Agent同时执行,最终指标结果会自动累加,可以通过提升Agent来模拟更高的并发能力
3.1. 思路
- 先做好单独接口的压测,大概掌握每个接口的指标情况
- 同时压测多个接口,完成对场景的压测
- 通过压测观察应用服务器、基础服务器的CPU、带宽、内存等指标,观察Redis、数据库、消息队列等基础组件情况,根据压测的返回结果得到每个场景的基础指标
- 通过分析发现瓶颈、然后再考虑如何突破瓶颈,提升QPS、吞吐量等
3.2. 如何做?
了解到单个Agent同时执行多个任务会进行排队,无法做到多任务同时执行,那么我们可以通过多个Agent同时执行不同的任务来模拟用户访问页面。
3.2.1. 构建Agent
之前与开发沟通得到每个页面最多可发送的请求是6个,那么我们准备6个Agent,分别为Agent1、Agent2、Agent3、Agent4、Agent5、Agent6
我们这里使用Docker来启动Agent、Agent对内开放端口: 5010、对外端口随机,镜像使用我们自建的: doddgu/crankagent:net5.0
并新建load.yml为之后压测使用:
profiles: crankAgent1: jobs: load: endpoints: - http://localhost:5010 crankAgent2: jobs: load: endpoints: - http://localhost:5011 crankAgent3: jobs: load: endpoints: - http://localhost:5012 crankAgent4: jobs: load: endpoints: - http://localhost:5013 crankAgent5: jobs: load: endpoints: - http://localhost:5014 crankAgent6: jobs: load: endpoints: - http://localhost:5015
load.yml 中记录了所有的压测机信息,其信息一般不做修改,我们可以作为公共的配置来使用无需每个项目都单独维护一份新的
3.2.2. 构建压测脚本
在这里我们选择wrk2作为本次基准测试工具,选择wrk2的原因是:
- 支持随机参数
- 可支持设置恒定的吞吐量负载
- 具备wrk的所有功能
此时我们针对ProjectA项目新建配置:project.profiles.yml,作为本次压测的环境配置来使用,其配置如下
imports: - https://raw.githubusercontent.com/doddgu/crank/sample/samples/wrk2/common/load.profiles.yml # 这边建议使用远程load.profiles.yml地址。(如果输入的是本地路径、则需输入与当前命令所在路径的相对路径) profiles: local: # 本地环境 variables: serverAddress: localhost # 应用服务域 serverPort: 80 # 应用服务端口 connections: 256 # 每个线程处理时保持打开的 HTTP 连接总数 N = 连接数/线程数 threads: 32 # 线程数 warmup: 3 # 预热时间: 3s duration: 180 # 测试时长: 3分钟 rate: # 吞吐量参数(每秒总请求数)
project.profiles.yml中记录了指定项目的各环境的配置,项目自己独立维护即可
除了项目信息、压测机配置之外,我们还需要有地方维护我们压测的接口信息,这边我的做法是将api独立拆分出来,每个yml只配置一个接口的压测信息,至于为什么不放到一块,而要单独拆分开呢?
这块考虑到我们压测的最小单元是API接口,如果把API接口独立拆分开,那么可以对单接口压测,而如果我们需要场景压测,也可以通过组合接口完成多接口同时压测,并且一旦我们完成了某个接口的压测编写,后续不需要再改动这个配置,如果我们按照场景拆分成不同的yml,在yml中再根据定义不同的scenario来做,那么后续场景新增加接口,还需要再更改这个场景的yml,并且scenario中的场景实际上也是根据接口维度区分的,目前crank并不能完成单个场景任务同时处理,基于以上原因,这边我们新调整好的配置格式为:
新增load.benchmarks.yml
imports: - https://raw.githubusercontent.com/doddgu/crank/sample/src/Microsoft.Crank.Jobs.Wrk2/wrk2.yml - https://raw.githubusercontent.com/doddgu/crank/sample/samples/wrk2/common/project.profiles.yml jobs: server: source: repository: https://github.com/doddgu/crank branchOrCommit: sample project: samples/hello/hello.csproj readyStateText: Application started. scenarios: api: application: # 实际压测项目时可移除此节点,此处是为模拟应用服务启动 job: server variables: duration: 1 load: job: wrk2 variables: serverPath: /user/get script: request.lua duration: 1 profiles: defaultParamLocal: # 本地环境的参数信息 variables: serverQueryString: ?id={1} serverQueryParameter: 1||2 # 随机请求/get?id=1、/get?id=2
按照此格式保存,后续新增接口也可以快速复制,简单修改即可快速完成压测工作的编写,这样一来,如果我们希望对localhost:5000/user/get这个接口做压测,仅需要在crank控制端输入:
crank --config load.benchmarks.yml --scenario api --load.framework net5.0 --application.framework net5.0 --profile local --profile crankAgent1 --description "获取用户详情" --profile defaultParamLocal
3.2.3. 构建批处理命令
但作为一个开发人员,总是希望事情能更简单一点,每次输入命令太麻烦了,所以就想到了通过批处理快速完成任务的发送,最终的项目结构就变成了
benchmarks ├─ defaultTitle 接口名称( Description ) └─ load.bat 最终执行的脚本,其中指定了要指定的yml配置、场景、以及任务环境是.net 5.0 └─ load.benchmarks.yml yml配置 └─ load.local.bat 测试本地环境时要执行的脚本、格式:load.{环境}.bat └─ README.md 帮助文档
每次通过双击load.{环境}.bat就完成了对当前接口的压力测试,然后就是等待结果输出……
| application | | | --------------------- | -------------- | | CPU Usage (%) | 1 | | Cores usage (%) | 10 | | Working Set (MB) | 85 | | Private Memory (MB) | 278 | | Build Time (ms) | 3,469 | | Start Time (ms) | 352 | | Published Size (KB) | 93,323 | | .NET Core SDK Version | 5.0.404 | | ASP.NET Core Version | 5.0.13+55738ff | | .NET Runtime Version | 5.0.13+b3afe99 | | load | | | --------------------- | -------------- | | Build Time (ms) | 3,281 | | Start Time (ms) | 0 | | Published Size (KB) | 74,276 | | .NET Core SDK Version | 5.0.404 | | ASP.NET Core Version | 5.0.13+55738ff | | .NET Runtime Version | 5.0.13+b3afe99 | | First Request (ms) | 86 | | Requests/sec | 2 | | Requests | 2 | | Mean latency (ms) | 2.68 | | Max latency (ms) | 2.68 | | Bad responses | 0 | | Socket errors | 0 | | Latency 50th (ms) | 2.68 | | Latency 75th (ms) | 2.68 | | Latency 90th (ms) | 2.68 | | Latency 99th (ms) | 2.68 | | Latency 99.9th (ms) | 2.68 | | Latency 99.99th (ms) | 2.68 | | Latency 99.999th (ms) | 2.68 |
3.2.4. 构建场景压测批处理命令
通过上面的一番操作,我们已经可以很容易的对单接口进行压测,但目前想模拟完成多接口同时压测,还需要再改造一下,之前我们想到,crank目前只能完成单独压测任务,那是不是有多个Agent,每个Agent单独压测一个接口,并同时启动多个Agent同时压测是不是可以模拟出来场景压测,那我通过批处理任务多点几次不同的接口压测不就可以了,基于以上考虑,又做了一个批处理脚本,用于调用多个接口的压测任务启动,最后的结构如下所示:
Crank ├─ benchmarks 压测脚本 │ ├─ api 接口压测脚本 │ │ ├─ add │ │ └─ get │ ├─ scipts lua脚本 │ │ ├─ common lua公共脚本 │ │ │ ├─ oauth.lua 认证lua脚本 │ │ │ ├─ util.lua lua工具类脚本 │ │ ├─ request.lua 封装请求lua脚本 │ ├─ scripts.tar lua脚本压缩包 ├─ common │ ├─ load.profiles.yml agent 负载机配置 │ ├─ project.profiles.yml 项目配置 │ ├─ scripts.profiles crank 执行script配置,用于对输出结果的二次处理 │ ├─ project.profiles.yml 项目配置 ├─ scripts 场景压测脚本 │ ├─ 用户.bat 用户压测 └─ env 环境配置,标记当前需要压测的环境在哪个配置文件中存储 └─ env.local 本地环境,存储本地环境的配置信息 └─ README.md 帮助文档
4. 结尾
通过上面的操作我们已经完成了对单接口以及单场景的压测,通过控制台可以清晰的看到每个接口的压测结果,我们只需要耐心等待压测任务结束,并整理压测结果数据,最后进行汇总我们的任务就完成了,但压测结果的收集也是一个费事费力的工作,作为一个开发,是不想把时间花费到这些整理表格的事情上,那我们如何做可以把整理表格数据的工作节省下来让我们可以歇会儿呢……
源码地址:https://github.com/doddgu/crank/tree/sample/samples/wrk2
参考链接:
开源地址
MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks
MASA.Contrib:https://github.com/masastack/MASA.Contrib
MASA.Utils:https://github.com/masastack/MASA.Utils
MASA.EShop:https://github.com/masalabs/MASA.EShop
MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor
如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们