8.7 KiB
IM 单机 60% 容量压力测试方案
日期:2026-09-29
本方案首轮执行结果见 IM-单机60%容量压力测试结果-20260929.md。
1. 目标与口径
本方案用一台物理压测机完成单聊、群聊和混合场景,容量按双机正式方案全局规模的 60% 计算。
单机不等于只运行一个 agent。为保留现有好友对和投递契约,同一台物理机上同时运行逻辑 agent A 和 agent B,每个 agent 承载 600 个连接,合计 1200 个 WebSocket。Coordinator 仍使用 expected-agents=2。
| 项目 | 双机正式方案 | 单机 60% 方案 |
|---|---|---|
| 物理压测机 | 2 | 1 |
| 逻辑 agent | 2 | 2 |
| 每个 agent 连接 | 1000 | 600 |
| WebSocket 总连接 | 2000 | 1200 |
| 好友对 | 1000 | 600 |
| 群成员 | 2000 | 1200 |
2. 场景容量
2.1 单聊
- 600 对好友,A/B 各 600 人。
- 每个用户发送 10 条,每秒 1 条。
- 总发送尝试 12000 条,是双机 20000 条的 60%。
- 每条 ACK 消息应在配对用户的 WebSocket 到达一次。
2.2 群聊
- 1200 个在线群成员。
- A/B 各取前 300 个用户作为发送者,合计 600 个发送者。
- 每人发送 2 条,全局每 100ms 一条,合计 1200 条群消息,耗时约 120 秒。
- 全部发送被 ACK 时,期望实时投递
1200 x 1200 = 1,440,000次。
2.3 单聊和群聊混合
- 前 180 对好友互发,每人 5 条,共 1800 条单聊。
- A/B 各 150 个群发送者,每人 2 条,共 600 条群消息。
- 群消息期望实时投递
600 x 1200 = 720,000次。 - 单聊和群聊同时开始,不分两轮单独执行。
工具按 users_per_machine 等比计算发送者数量。users_per_machine=1000 时保持原双机参数,设为 600 时自动使用上述单机参数。
3. 文件隔离
单机方案不复用双机的运行文件,避免把 1200 人结果和 2000 人结果混在一起。
loadtest/im-pressure/config.single-machine.json
loadtest/im-pressure/manifest.single-machine.json
loadtest/im-pressure/results/<RUN_ID>/
从模板生成本地配置:
cp loadtest/im-pressure/config.single-machine.example.json \
loadtest/im-pressure/config.single-machine.json
chmod 600 loadtest/im-pressure/config.single-machine.json
修改租户、HTTP/WS 地址和注册手机前缀。A/B endpoint 可以填同一个公网入口;若需确认跨 Pod 投递,应分别填写两个定向入口。
4. 一次性数据准备
source loadtest/im-pressure/.env.test
go build -o loadtest/im-pressure/im-pressure ./loadtest/im-pressure
ulimit -n 100000
./loadtest/im-pressure/im-pressure prepare \
-config loadtest/im-pressure/config.single-machine.json \
-manifest loadtest/im-pressure/manifest.single-machine.json
./loadtest/im-pressure/im-pressure verify \
-config loadtest/im-pressure/config.single-machine.json \
-manifest loadtest/im-pressure/manifest.single-machine.json
verify 必须输出 1200 个账号、600 对好友和 1200 个群成员。单机 manifest 可以复用已注册的 lta0001..0600 和 ltb0001..0600 账号,但必须使用独立群 Topic。
5. 正式运行
以群聊为例:
export RUN_ID='single60-group-r1-YYYYMMDD-01'
export SCENARIO='group'
mkdir -p "loadtest/im-pressure/results/${RUN_ID}"
终端 1,启动 coordinator:
./loadtest/im-pressure/im-pressure coordinator \
-listen :8090 \
-run-id "${RUN_ID}" \
-scenario "${SCENARIO}" \
-expected-agents 2 \
-output "loadtest/im-pressure/results/${RUN_ID}/coordinator.json"
终端 2,启动逻辑 agent A:
./loadtest/im-pressure/im-pressure agent \
-config loadtest/im-pressure/config.single-machine.json \
-manifest loadtest/im-pressure/manifest.single-machine.json \
-machine a \
-coordinator http://127.0.0.1:8090 \
-metrics-listen :9105 \
-output "loadtest/im-pressure/results/${RUN_ID}/agent-a-summary.json"
终端 3,启动逻辑 agent B:
./loadtest/im-pressure/im-pressure agent \
-config loadtest/im-pressure/config.single-machine.json \
-manifest loadtest/im-pressure/manifest.single-machine.json \
-machine b \
-coordinator http://127.0.0.1:8090 \
-metrics-listen :9106 \
-output "loadtest/im-pressure/results/${RUN_ID}/agent-b-summary.json"
两个 agent 都报告 600/600 READY 并稳定通过 30 秒屏障后才会开始。任一 agent 未满连接时 coordinator 会撤销开始时间。
固定 drain 结束后,agent 还会等待入站投递连续静默后再生成实时 summary。默认 delivery_quiet_period=10s、delivery_quiet_timeout=3m;接收计数增长或回调队列不为空都会重置静默窗口,避免公网晚到帧被误判为实时丢失。
6. 指标采集
终端 4 在起压前至少 2 分钟启动:
./loadtest/im-pressure/im-pressure collect \
-duration 20m -interval 5s \
-coordinator http://127.0.0.1:8090 \
-coordinator-output "loadtest/im-pressure/results/${RUN_ID}/coordinator.json" \
-url agent-a=http://127.0.0.1:9105/metrics \
-url agent-b=http://127.0.0.1:9106/metrics \
-url node=http://127.0.0.1:9100/metrics \
-output "loadtest/im-pressure/results/${RUN_ID}/metrics.jsonl"
同时采集 Redis、Kafka、MySQL、Pod CPU/内存/网络、Kafka consumer lag、MySQL 连接池等指标。如本机没有 node-exporter :9100,删除 node URL,但不影响 agent 指标采集。
发版后必须在服务端 /metrics 确认 im_ws_heartbeat_seconds{setting="ping_period"}=15、im_ws_heartbeat_seconds{setting="pong_wait"}=90,并采集 im_ws_heartbeat_total{event="pong_received"} 和 im_ws_disconnect_total{reason="timeout"} 的分 Pod 增量。
混合场景还必须采集 im_ctrl_ack_delivery_duration_seconds{stage="enqueue"} 和 im_ctrl_ack_delivery_duration_seconds{stage="write"}。agent 端同时采集 im_load_agent_publish_ctrl_observed_total、im_load_agent_publish_ctrl_over_20s_total、im_load_agent_publish_ctrl_late_total 和 im_load_agent_publish_ctrl_unresolved,用于区分及时 ACK、超过 20 秒的 ACK、超过命令超时后晚到的 ACK 与最终未到 ACK。
7. 报告和独立落库核验
./loadtest/im-pressure/im-pressure merge \
-a "loadtest/im-pressure/results/${RUN_ID}/agent-a-summary.json" \
-b "loadtest/im-pressure/results/${RUN_ID}/agent-b-summary.json" \
-output "loadtest/im-pressure/results/${RUN_ID}/report.json"
export MYSQL_DSN='readonly:password@tcp(mysql-host:3306)/meeting_im?parseTime=true'
export TENANT='006' # 必须与 config.single-machine.json 中的 tenant 一致
./loadtest/im-pressure/im-pressure verify-db \
-coordinator "loadtest/im-pressure/results/${RUN_ID}/coordinator.json" \
-tenant "${TENANT}" \
-dsn-env MYSQL_DSN \
-registry-db hotgo \
-output "loadtest/im-pressure/results/${RUN_ID}/db-verification.json"
8. 验收门禁
- READY 必须是
1200/1200,统计窗口内断连为 0。 - publish 明确拒绝和结果未知均为 0,成功率至少 99.99%。
publish_ctrl_unresolved必须为 0;publish_ctrl_over_20s和publish_ctrl_late作为定位指标单独报告,不与消息丢失混为一项。- ACK P95 和投递 P95 保留记录用于趋势对比,不作为当前公网单机容量测试的阻断门禁。
- 实时缺失、重复、乱序、未知本轮消息均为 0。
- ACK 台账和无 ACK 但已观测台账的历史回查命中率必须 100%,独立 MySQL 核验丢失、错配、重复均为 0。
- 实时发送、历史回查和渐进关闭三个阶段分别统计 Topic/Proxy/Session 队列满,不能只看整轮总数。
9. 单机特有风险
- 两个 agent 共用一台机器的 CPU、内存、FD、NAT 和出口带宽,测到的是“单机压测端 + 服务端”综合上限,不能代替双机最终验收。
- 正式前做 120/600/1200 连接三档阶梯,观察 agent CPU、
go_goroutines、FD、接收速率和 Ping/Pong 断连,确认压测端本身没有先饱和。当前工具已将投递观测按client_mid分片、接收账号顺序状态独立加锁,使用原子延迟直方图,并将数据统计回调从 WebSocket socket 读循环解耦;仍需用指标证明本机有余量。 - 两个 agent 必须使用不同指标端口,不能同时绑定
:9105。 - 压测域名如被 VPN/TUN 接管,起压前必须确认所有当前 IP 的路由都指向物理网卡。
- 单机 60% 通过后只能进入双机 100% 验收,不能直接得出 2000 连接容量通过的结论。
断连时同时查看 agent summary 中的 disconnect_reasons 和 disconnect_samples。前者按 timeout、abnormal close、connection reset 等原因分类计数,后者最多保留 100 条带账号与时间的原始错误样本,用于区分压测端读循环阻塞、网络中断和服务端主动关闭。