游戏服务器上线前,必须做压力测试。我们当时用 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
|
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
|
安装步骤:
- 关闭 JMeter
- 将插件文件复制到
JMETER_HOME/lib/ 和 JMETER_HOME/lib/ext/
- 重启 JMeter
- 菜单栏 → Options → Plugins Manager 验证安装
3. Linux 环境安装
1 2 3 4 5 6
| sudo mkdir -p /usr/lib/jvm sudo tar -zxvf jdk-8u191-linux-x64.tar.gz -C /usr/lib/jvm
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
| source /etc/profile
java -version
tar -zxvf apache-jmeter-5.4.1.tgz -C /opt/
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
| 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
sudo sh jmeter.sh -n -t tps.jmx -l tps.jtl
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
export HEAP="-Xms2g -Xmx8g" sh jmeter.sh -n -t tps.jmx
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
| htop
mpstat -P ALL 1
free -h watch -n 1 free -h
netstat -an | grep ESTABLISHED | wc -l ss -s
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
|
for i in {1..10000}; do echo "user_$i,password_$i,token_$i" >> users.csv done
|
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
|
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 压力测试的几个关键点:
- 环境准备:安装 JMeter 和 WebSocket 插件
- 场景设计:模拟真实用户行为和业务场景
- 参数配置:合理设置并发数、Ramp-Up 时间
- 分布式执行:使用 Linux 命令行进行大规模压测
- 结果分析:关注 TPS、响应时间、错误率、资源使用
- 瓶颈定位:结合服务器监控找出性能瓶颈
通过科学的压测方法,可以有效评估服务器容量,为系统优化和扩容提供数据支持。