WebSocket 性能测试:JMeter 压力测试实战记录

游戏服务器上线前,必须做压力测试。我们当时用 JMeter 来压 WebSocket,记录一下完整的流程。

为什么要做 WebSocket 压测

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│ WebSocket 压测目的 │
├─────────────────────────────────────────────────────────────┤

│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ │ 评估容量 │ │ 发现瓶颈 │ │ 验证优化 │
│ │ 最大并发数 │ │ CPU/内存/IO │ │ 调优效果验证 │
│ └──────────────┘ └──────────────┘ └──────────────┘

│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ │ 稳定性测试 │ │ 性能基线 │ │ 渠道验收 │
│ │ 长时间运行 │ │ 建立性能指标 │ │ 满足渠道要求 │
│ └──────────────┘ └──────────────┘ └──────────────┘

核心指标

指标 说明 目标值参考
TPS (Transactions Per Second) 每秒事务数 根据业务需求
并发连接数 同时在线的 WebSocket 连接 通常 1000-10000
响应时间 消息往返延迟 < 100ms
错误率 失败请求比例 < 0.1%
CPU 使用率 服务器 CPU 占用 < 80%
内存占用 服务器内存使用 < 80%

环境搭建

1. JMeter 安装(Windows)

1
2
3
4
5
6
7
8
9
10
# 1. 下载 JMeter
# 官网:https://jmeter.apache.org/download_jmeter.cgi
# 下载 apache-jmeter-5.x.zip

# 2. 解压到指定目录
# 如:D:\apache-jmeter-5.4.1

# 3. 启动 JMeter
cd D:\apache-jmeter-5.4.1\bin
jmeter.bat

2. 插件安装

JMeter 默认不支持 WebSocket,需要安装插件:

必需插件文件:

1
2
3
4
5
6
lib/
├── jmeter-plugins-cmn-jmeter-0.4.jar
└── ext/
├── jmeter-plugins-manager-0.20.jar
├── jmeter-plugins-graphs-basic-2.0.jar
└── jmeter-websocket-samplers-1.1.jar

安装步骤:

  1. 关闭 JMeter
  2. 将插件文件复制到 JMETER_HOME/lib/JMETER_HOME/lib/ext/
  3. 重启 JMeter
  4. 菜单栏 → Options → Plugins Manager 验证安装

3. Linux 环境安装

1
2
3
4
5
6
# 1. 安装 Java 环境
sudo mkdir -p /usr/lib/jvm
sudo tar -zxvf jdk-8u191-linux-x64.tar.gz -C /usr/lib/jvm

# 2. 配置环境变量
sudo vim /etc/profile

添加以下内容到 /etc/profile

1
2
3
4
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_191
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
1
2
3
4
5
6
7
8
9
10
11
# 3. 生效配置
source /etc/profile

# 4. 验证 Java 安装
java -version

# 5. 上传 JMeter 并解压
tar -zxvf apache-jmeter-5.4.1.tgz -C /opt/

# 6. 添加 JMeter 到 PATH
export PATH=/opt/apache-jmeter-5.4.1/bin:$PATH

测试计划配置

测试场景设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────┐
│ WebSocket 测试场景 │
├─────────────────────────────────────────────────────────────┤

│ 阶段 1:连接建立
│ ├── 1000 并发用户同时建立 WebSocket 连接
│ └── 验证连接成功率和建立时间

│ 阶段 2:消息发送
│ ├── 每个连接发送 100 条消息
│ ├── 消息间隔:100ms
│ └── 消息类型:文本/二进制

│ 阶段 3:并发事务
│ ├── 模拟游戏场景(登录、匹配、移动、攻击)
│ └── 验证事务响应时间

│ 阶段 4:持续运行
│ ├── 保持连接 30 分钟
│ └── 检测内存泄漏和资源回收

详细配置步骤

1. 创建线程组

路径: Test Plan → Add → Threads → Thread Group

1
2
3
4
Thread Group
├── Number of Threads (users): 1000 # 并发用户数
├── Ramp-Up Period (in seconds): 0 # 启动时间,0表示立即全部启动
└── Loop Count: 10 # 循环次数

配置说明:

参数 说明
Number of Threads 1000 模拟 1000 个并发用户
Ramp-Up Period 0 立即启动所有线程,测试峰值压力
Ramp-Up Period 60 60 秒内逐渐启动,模拟真实场景
Loop Count 10 每个线程执行 10 次
Loop Count Infinite 持续运行,用于稳定性测试

2. 添加计数器(参数化)

路径: Thread Group → Add → Config Element → Counter

1
2
3
4
5
Counter
├── Start Value: 1 # 起始值
├── Increment: 1 # 步长
├── Maximum value: 999999999 # 最大值
└── Reference Name: id # 变量名,后续用 ${id} 引用

用途: 生成唯一用户 ID,避免重复登录冲突

3. WebSocket 连接配置

路径: Thread Group → Add → Sampler → WebSocket Open Connection

1
2
3
4
5
6
WebSocket Open Connection
├── Server Name or IP: ws.example.com
├── Port Number: 8080
├── Path: /game
├── Connection timeout (ms): 5000
└── Read timeout (ms): 60000

4. 发送消息配置

路径: Thread Group → Add → Sampler → WebSocket request-response Sampler

1
2
3
4
5
6
7
8
9
WebSocket request-response Sampler
├── Connection: use existing connection
├── Request data:
│ {
│ "type": "login",
│ "userId": "${id}",
│ "timestamp": ${__time()}
│ }
└── Response timeout (ms): 5000

消息模板示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 登录消息
{
"cmd": "login",
"uid": "${id}",
"token": "test_token_${id}"
}

// 游戏动作消息
{
"cmd": "move",
"x": ${__Random(0,800)},
"y": ${__Random(0,600)},
"timestamp": ${__time()}
}

// 心跳消息
{
"cmd": "ping",
"time": ${__time()}
}

5. 循环控制器(增加事务量)

路径: Thread Group → Add → Logic Controller → Loop Controller

1
2
3
4
5
Loop Controller
├── Loop Count: 100 # 循环 100 次

├── WebSocket request-response Sampler (游戏操作)
└── Constant Timer (100ms) # 每次间隔 100ms

6. 添加监听器

路径: Thread Group → Add → Listener

1
2
3
4
5
Listeners
├── View Results Tree # 查看详细请求/响应
├── Aggregate Report # 汇总统计报告
├── Graph Results # 图形化结果
└── jp@gc - Transactions per Second # TPS 图表

Linux 命令行执行

1. 拷贝测试计划

1
2
# 将 Windows 上编辑好的测试计划上传到 Linux
scp test_plan.jmx root@server:/opt/jmeter/

2. 执行压测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cd /opt/apache-jmeter-5.4.1/bin

# 非 GUI 模式执行
sudo sh jmeter.sh -n -t tps.jmx -l tps.jtl

# 参数说明
# -n: 非 GUI 模式
# -t: 测试计划文件
# -l: 结果日志文件
# -j: JMeter 日志文件
# -e -o: 生成 HTML 报告到指定目录

# 生成 HTML 报告
sudo sh jmeter.sh -n -t tps.jmx -l tps.jtl -e -o /opt/jmeter/report

3. 高级执行选项

1
2
3
4
5
6
7
8
9
# 分布式压测(多机负载)
sh jmeter.sh -n -t tps.jmx -l tps.jtl -R server1,server2,server3

# 指定 JVM 内存
export HEAP="-Xms2g -Xmx8g"
sh jmeter.sh -n -t tps.jmx

# 指定 Java 选项
JVM_ARGS="-Xms4g -Xmx16g -XX:+UseG1GC" sh jmeter.sh -n -t tps.jmx

结果分析

1. 聚合报告解读

指标 含义 健康标准
Samples 总请求数 -
Average 平均响应时间(ms) < 100ms
Median 中位数响应时间(ms) < 100ms
90% Line 90% 请求响应时间 < 该值 < 200ms
95% Line 95% 请求响应时间 < 该值 < 500ms
99% Line 99% 请求响应时间 < 该值 < 1000ms
Min/Max 最小/最大响应时间 -
Error % 错误率 < 0.1%
Throughput 吞吐量(req/s) 根据业务需求
KB/sec 数据传输速率 -

2. TPS 图表分析

1
2
3
4
5
6
7
8
9
10
TPS (Transactions Per Second)

│ ╭────╮
│ ╱ ╲ ╭────────── 目标 TPS 线
│ ╱ ╲ ╱
│ ╱ ╲─╱
│╱ ╲
│ ╲_____ 稳定期 TPS
└─────────────────────────────────────► 时间
启动期 峰值期 稳定期

关键观察点:

  • TPS 峰值:系统能达到的最大处理能力
  • 稳定 TPS:长时间运行的平均处理能力
  • TPS 下降:可能出现了性能瓶颈

3. 服务器资源监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# CPU 监控
htop
# 或
mpstat -P ALL 1

# 内存监控
free -h
watch -n 1 free -h

# 网络连接监控
netstat -an | grep ESTABLISHED | wc -l
ss -s

# WebSocket 连接数
grep "websocket" /var/log/game/server.log | wc -l

实际压测案例

测试环境配置

1
2
3
4
5
6
服务器配置:
├── CPU: 16 核
├── RAM: 32 GB
├── DISK: 100 GB SSD
├── 服务实例: 12 个(PM2 cluster)
└── 网络带宽: 100 Mbps

测试结果

指标 结果
最大并发连接 5000+
TPS 5000
平均响应时间 15ms
错误率 0.01%
CPU 使用率 65%
内存使用 18GB

瓶颈分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────┐
│ 性能瓶颈诊断 │
├─────────────────────────────────────────┤

│ TPS 在 5000 达到瓶颈
│ │
│ ▼
│ 可能原因:
│ ┌──────────┐ ┌──────────┐
│ │ CPU 瓶颈 │ │ 内存瓶颈 │
│ │ 65% 使用 │ │ 56% 使用 │
│ │ 非瓶颈 │ │ 非瓶颈 │
│ └──────────┘ └──────────┘
│ │
│ ▼
│ ┌────────────────────────┐
│ │ 网络带宽瓶颈 │
│ │ 100 Mbps 接近饱和 │
│ │ 升级带宽可提升 TPS │
│ └────────────────────────┘

最佳实践

1. 测试数据准备

1
2
3
4
5
6
7
# 生成测试用户数据
#!/bin/bash
for i in {1..10000}; do
echo "user_$i,password_$i,token_$i" >> users.csv
done

# JMeter 中使用 CSV Data Set Config 读取

2. 渐进式压测

1
2
3
4
5
阶段 1: 100 并发  ──► 验证基本功能
阶段 2: 500 并发 ──► 评估正常负载
阶段 3: 1000 并发 ──► 评估峰值负载
阶段 4: 2000 并发 ──► 寻找性能拐点
阶段 5: 5000 并发 ──► 验证极限容量

3. 监控告警配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 压测期间服务器监控脚本
#!/bin/bash
while true; do
cpu=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
mem=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100.0}')
conn=$(netstat -an | grep :8080 | grep ESTABLISHED | wc -l)

echo "$(date): CPU=${cpu}%, MEM=${mem}%, CONN=${conn}"

if (( $(echo "$cpu > 90" | bc -l) )); then
echo "ALERT: CPU usage high!" | tee -a alert.log
fi

sleep 5
done

4. 常见错误处理

错误 原因 解决方案
Connection refused 服务器未启动/端口错误 检查服务状态和防火墙
Read timeout 响应超时 增加超时时间或优化服务器
Connection closed 连接被关闭 检查服务器并发处理能力
OutOfMemoryError JMeter 内存不足 增加 JVM 堆内存

写在最后

WebSocket 压力测试的几个关键点:

  1. 环境准备:安装 JMeter 和 WebSocket 插件
  2. 场景设计:模拟真实用户行为和业务场景
  3. 参数配置:合理设置并发数、Ramp-Up 时间
  4. 分布式执行:使用 Linux 命令行进行大规模压测
  5. 结果分析:关注 TPS、响应时间、错误率、资源使用
  6. 瓶颈定位:结合服务器监控找出性能瓶颈

通过科学的压测方法,可以有效评估服务器容量,为系统优化和扩容提供数据支持。