Files
im_server/docs/test/IM-单机60%容量压力测试结果-20260929.md
T

16 KiB
Raw Blame History

IM 单机 60% 容量压力测试结果

日期:2026-09-29

1. 结论

本轮使用一台物理压测机同时运行逻辑 agent A/B,每侧 600 个 WebSocket,合计 1200 连接。60% 容量的正确性和性能总验收未通过。

  • 单聊实时和持久化正确性通过,但 ACK P95 和投递 P95 未达标。
  • 群聊在 1200 条群消息、144 万设计投递量下出现 ACK 超时、连接重置和分钟级投递延迟。
  • 混合场景明显越过容量拐点,737 个连接断开,仅 44.83% 发送得到 ACK。
  • 独立 MySQL 核验确认:三个场景的所有 ACK 消息均精确落库,0 错配、0 重复;所有“无 ACK 但 WS 已观测”消息也均已落库。
  • 压测工具本轮峰值 RSS 不超过 127 MB,每个 agent 峰值 FD 614,未显示本机内存或 FD 先饱和。
  • 混合实时阶段的群 Topic 所在 im-server-0 出现 broadcast queue full,服务端 Topic 处理能力是已证实的瓶颈之一。

2. 环境与口径

  • 租户:allrich
  • HTTP/WS 入口:api.118im.com
  • 物理压测机:1 台
  • 逻辑 agent:A/B 各2个中的一个,每侧 600 连接
  • 账号:1200,好友对:600,群成员:1200
  • API 域名当前 3 个 IP 均经 en1 和局域网网关直连,未走 VPN/TUN
  • IM Server:3 Pod,无重启;每 Pod request 1 CPU/512Mi,limit 2 CPU/1Gi
  • 独立群与 manifest:loadtest/im-pressure/manifest.single-machine.json

准备阶段最终验证为 accounts=1200 pairs=600 group_members=1200。群批量邀请曾出现入口 502 和幂等任务长时间处理中,工具通过按实际成员补齐缺失用户完成恢复。

3. 结果汇总

场景 Run ID ACK 实时投递 断连 ACK P95 投递 P95 独立 DB 核验 结果
单聊 s60-p2p-r2-0929 12000/12000 12000/12000 0 1341.555ms 2000ms 12000 ACK 全部精确命中 性能未通过
群聊 s60-group-r1-0929 1145/1200 1350508/1374000 ACK 台账 47 11207.859ms 94621.882ms 1145 ACK + 39 无 ACK 落库 未通过
混合 s60-mixed-r1-0929 1076/2400 234829/263657 ACK 台账 737 17491.634ms 101226.392ms 1076 ACK + 1017 无 ACK 落库 未通过

表中群聊和混合的“期望投递”只按有 ACK 的 ledger 计算。发送结果未知的消息不能在没有 seq 时直接加入期望投递分母,因此该分母小于设计投递量。

4. 分场景记录

4.1 单聊

R1 的历史查询将 limit=10 错误地按目标消息数计算,没有计入 P2P Topic 中双方交错的 seq,造成 4619 条假缺失。修复后 R2 结果:

  • 12000 条全部 ACK,无结果未知。
  • 12000 次对端投递全部到达,无重复、乱序、断连。
  • 12000 条历史回查全部命中,持久化缺失 0。
  • ACK P95 1341.555ms,超过 500ms 门禁;同机投递 P95 落在 2000ms 桶,超过 1000ms 门禁。

4.2 群聊

  • 发送 1200 条,1145 条 ACK,43 条 ACK 超时,12 条 socket 结果未知,无明确拒绝。
  • 47 个连接断开:22 个 abnormal close,25 个 connection reset by peer。
  • ACK 台账下实时缺失 23492 次,这些消息最终均可从历史查到。
  • 55 条结果未知中,39 条被接收端实时观测,且全部被独立 SQL 确认落库;另 16 条既无 ACK、无 WS 观测,也无数据库记录。
  • 断连发生时间为 03:40:10Z 至 03:41:15Z,与群实时发送阶段重合。

4.3 混合

  • 设计发送为 1800 条单聊和 600 条群聊,合计 2400 条。
  • 1076 条 ACK,1324 条结果未知,其中 1036 条 ACK 超时、288 条 socket 失败,无明确拒绝。
  • 737 个连接断开:69 个 abnormal close,668 个 connection reset by peer。
  • ACK 台账下实时缺失 28828 次,其中 28633 次对应消息最终已查到持久化记录。
  • 历史验证 ledger 含 2002 条,命中 1331 条,缺失 671 条全部为 P2P。独立 SQL 确认所有 1076 条 ACK 消息和全部 926 条 WS 已观测的无 ACK 消息均已落库,因此这 671 条是 verifier 连接掉线和 3 分钟总预算造成的验证不完整,不是数据库丢失。
  • 1324 条结果未知中,1017 条实际落库:926 条被 WS 观测,另有 91 条未被 WS 观测但已落库。剩余 307 条无 ACK、无 WS 观测、无数据库记录。

4.4 独立 MySQL 核验

核验租户 ID 为 8,消息表为 im_008_messages 和 im_008_group_messages_202609。

场景 ACK expected 精确命中 ACK 缺失 错配 重复 无 ACK 但落库
单聊 12000 12000 0 0 0 0
群聊 1145 1145 0 0 0 39
混合 1076 1076 0 0 0 1017

verify-db 对无 ACK 但落库的行按 unexpected 报告,所以群聊和混合命令会以非零状态结束;这不代表数据错误,而是发送结果未知但服务端已持久化的重要诊断证据。

5. 服务端观测

  • im-server-0/1/2 全部 Running,本轮重启数均为 0。
  • 混合实时阶段 03:53:00Z..03:53:02Z,im-server-0 记录 55 次 topic's broadcast queue is full。
  • 混合渐进关闭末尾 04:03:45Z,im-server-0 再记录 375 次同类队列满。当前 60 秒 close ramp 仍未完全避免关闭风暴。
  • 同一观测窗口的 03:45:13Z,im-server-2 在 serveIMFriendRequestAction (server/im_http.go:6256) 出现一次 nil pointer panic。该端点不属于群实时发送路径,不将其归因为本轮断连,但需单独修复。
  • 当前 kubeconfig 无权读取 metrics-server,未能取得负载窗口的 Pod CPU/内存历史值。

6. 压测工具修复

  1. 新增单机 60% 配置和执行方案,A/B 各 600 连接。
  2. 群聊和混合发送者按 users_per_machine 比例缩放,1000 用户的原参数保持不变。
  3. 投递观测按 client_mid 分为 64 片,每个接收账号独立维护 seq,延迟直方图改为原子计数,移除 WS 读循环中的全局锁争用。
  4. summary/report 新增 disconnect_reasons 和最多 100 条 disconnect_samples。
  5. 群创建后立即 checkpoint manifest;批量邀请失败后重读实际成员,仅邀请缺失用户。
  6. P2P 历史查询 limit 按 seq 区间扩展,上限 200,修复双方消息交错造成的假缺失。

7. 已实施改进

  1. 好友申请接受和自动接受路径增加 store 空结果保护。底层异常返回 nil, nil 时记录租户、申请、操作人和状态并返回 HTTP 500,不再触发进程 panic。
  2. Hub 向已满的 Topic client queue 投递失败时,立即向原发送连接返回 503 service unavailable,并记录 topic、tenant、队列长度和容量。客户端可据此重试,压测也不再把这类请求归为等待超时的未知结果。
  3. 群消息权限检查改为复用群设置缓存,并为个人禁言增加 Redis 正向和负向缓存;新增禁言和解除禁言会同步更新缓存,到期禁言会自动淘汰。此改动减少群 Topic 串行处理路径上的 MySQL 查询。
  4. message_publish_stage_duration_seconds 新增 group/permission_check 和 */realtime_fanout 阶段,下轮可区分权限查询、持久化和实时扩散耗时。
  5. A/B 两个逻辑 agent 按账号顺序交错关闭连接。close_ramp=60s 时每个 agent 的本地关闭过程约 120 秒,A/B 相差半个时隙,整体约每 100ms 关闭一个连接,避免两侧同时下线形成双倍尖峰。
  6. 已增加空结果防 panic、Topic queue 满返回 503、双 agent 关闭时隙的自动化测试。

8. 复测顺序

  1. 发版后先跑单机群聊 60%,验收实时发送阶段和关闭阶段 topic_client enqueue failed 均为 0;若仍过载,发送端必须收到 503,不能再出现对应的纯 ACK timeout。
  2. 对比 permission_check、save、realtime_fanout 三阶段的 P50/P95/P99,确认群权限缓存是否消除了数据库检查瓶颈。
  3. 群聊通过后再跑单机混合 60%,要求 ACK、历史和独立 SQL 三方账本一致,连接断开数为 0。
  4. 补齐 Prometheus/metrics-server 权限并同步保存 Pod CPU、内存、网络、Kafka lag、Redis、MySQL 指标;在此之前不进行双机 100% 最终验收。

9. 发版后群聊 R2

Run ID:s60-group-r2-0929。

本轮开始前 3 个 Pod 均为 Running、0 重启,账号和群成员核验为 accounts=1200 pairs=600 group_members=1200。测试完整运行到 A/B 历史验证完成,并执行了错峰关闭和独立 MySQL 核验。

指标 结果
发送尝试 / ACK 1200 / 871
结果未知 329(222 timeout、107 socket)
断连 360(315 reset、44 abnormal close、1 timeout)
ACK P95 16630.441ms
投递 P95 101823.156ms
ACK 台账期望 / 实时收到 1045200 / 889777
实时缺失 155423,最终全部查到持久化
历史回查 1080 / 1080
DB:ACK 精确命中 871 / 871
DB:无 ACK 但落库 209
DB:缺失 / 错配 / 重复 0 / 0 / 0

329 条结果未知中,209 条已被 WS 观测且独立 SQL 确认落库,剩余 120 条数据库中不存在。断连样本主要是公网对端 connection reset by peer,且样本连接均指向 64.118.158.131:443;本机到域名全部当前 IP 均使用 en1 静态直连,没有经过 VPN/TUN。

服务端指标显示群 Topic 所在 im-server-0 的 1080 条 save 全部低于 100ms,Kafka consumer lag 为 0;但 topic_server 队列累计出现 393 次 full。压测客户端 CPU、RSS 不构成瓶颈。

本轮不能作为提交 75bb448 的有效回归:Pod 中运行的二进制不包含该提交新增的 permission_check、realtime_fanout 阶段字符串和好友申请空结果保护日志,Prometheus 也没有相应时序,说明实际部署镜像不是该提交构建结果。必须正确部署 75bb448 或后续提交后重跑。

此外,本轮发现 agent 只在正式开始前维护连接,统计窗口内断线后不会恢复,导致历史核验依赖剩余连接。后续已改为持续维护到 A/B 历史验证均完成,并将重连并发限制为每 agent 16,避免恢复风暴;每次计划发送前重新取得当前连接。

10. 产物

  • 方案:docs/test/IM-单机60%容量压力测试方案.md
  • 单聊 R2:loadtest/im-pressure/results/s60-p2p-r2-0929/
  • 群聊 R1:loadtest/im-pressure/results/s60-group-r1-0929/
  • 混合 R1:loadtest/im-pressure/results/s60-mixed-r1-0929/
  • 群聊 R2:loadtest/im-pressure/results/s60-group-r2-0929/

结果目录包含 coordinator、A/B summary、合并 report 和 agent 指标 JSONL。

12. 精确成员规模诊断复测

为区分“在线连接数”和“群成员总数”的影响,新增 300、600 人独立群复测。每个测试群在起压前均按用户 ID 集合核验,要求实际成员与目标账号完全相等。

场景 Run ID 在线/群成员 ACK 实时投递 历史 DB 精确命中 ACK P95 投递 P95
原 1200 人群、仅 300 人在线 s300-group-r1b-0929 300/1200 45/300 13500/13500(按 ACK 台账) 206/206 45 ACK + 210 结果未知全部落库 18994ms 89489ms
精确 300 人群 s300m-group-r1-0930 300/300 300/300 90000/90000 300/300 300/300 235.177ms 500ms 桶内
精确 600 人群 s600m-group-r1-0930 600/600 600/600 360000/360000 600/600 600/600 235.177ms 500ms 桶内
精确 900 人群 s900m-group-r1-0930 900/900 890/900 800555/801000(按 ACK 台账) 899/899 890 ACK + 9 结果未知落库 >10s,最大19.272s >10s,最大55.279s

三轮均未出现 WS 发送队列满、WS 写失败或 Kafka lag。精确 300、600 人群在实时阶段也没有新增 session_topic_publish、topic_client、topic_server 满队列,说明 WS 写链路在 600 在线规模内不是瓶颈。

原 1200 人群仅 300 人在线时有约 900 个离线成员。群消息处理会在 Topic 串行路径中为每个离线成员逐个调用 Redis presence 查询;上一条消息 ACK 后的推送准备持续占用 Topic goroutine,使后续消息长时间等待,最终表现为 ACK 超时和分钟级投递延迟。精确成员群消除这部分离线成员后,同等 300 在线规模恢复正常,验证了该路径是主要根因。

600 人实时发送通过后,240 秒错峰下线仍使群主 Pod 的 topic_server full 从 36 增至 128,净增 92;其他两类内部队列无增量。该问题来自最后会话离线时的 presence 扩散,与消息保存、Kafka 消费和 WS 实时投递无关,仍需在服务端合并或限流离线状态广播。

精确 900 人群出现容量拐点:900 次发送中 890 次 ACK,9 次 ACK 超时、1 次 socket 失败;7 条连接在实时阶段超时断开并自动恢复。按 ACK 台账计算的 445 次实时缺口全部可从历史补回,890 条 ACK 全部精确落库;9 条结果未知实际落库,1 条 socket 失败未落库。因此本轮没有持久化丢失,但实时正确性和延迟门禁未通过。

900 人实时阶段三个内部队列的 full 计数均无增量,Kafka lag 为 0。成功处理的 899 条消息中,permission_check P95 不超过 10ms、save P95 不超过 50ms、kafka_produce P95 不超过 25ms、realtime_fanout P95 不超过 5ms;抽样覆盖后半段 575468 次 WS 投递,发送队列等待 P95 不超过 10ms、socket write P95 不超过 0.5ms,且没有发送队列满或 text write 失败。

现有 realtime_fanout 指标在 pushForData 之前结束。pushForData 位于 Topic 串行处理路径中,每条群消息遍历全部成员、构造推送收件人并调用 sendPush;即使成员全部在线,900 人规模下这段未计时工作仍可让后续发布在 topic_client 中排队。队列未达到容量上限并不代表没有排队,这与 ACK 延迟逐步增长到 19 秒、投递延迟增长到 55 秒的现象一致。下一步应先补充 push_prepare、push_dispatch 和 Topic client queue wait 指标,再将群推送准备移出 Topic 串行热路径或改为异步批处理。

900 人在 240 秒错峰下线后,群主 Pod 的 topic_server full 从 0 增至 379,说明 presence 离线扩散问题随群规模明显放大。压测机两个 agent 的峰值 CPU 分别约 39.5% 和 38.5% 单核,峰值 RSS 各约 76MB、FD 各 460,不是本轮瓶颈。

新增产物:

  • 300 人精确群:loadtest/im-pressure/results/s300-group-members-setup-0929/
  • 300 人复测:loadtest/im-pressure/results/s300m-group-r1-0930/
  • 600 人精确群:loadtest/im-pressure/results/s600-group-members-setup-0930/
  • 600 人复测:loadtest/im-pressure/results/s600m-group-r1-0930/
  • 900 人精确群:loadtest/im-pressure/results/s900-group-members-setup-0930/
  • 900 人复测:loadtest/im-pressure/results/s900m-group-r1-0930/

13. 容量拐点修复(待发版复测)

针对精确 900 人群暴露的 Topic 串行积压和批量下线 presence 风暴,已完成以下代码改造:

  1. pushForData 在 Topic goroutine 内只复制成员推送快照,不再逐成员同步查询 Redis,也不再执行跨 Pod 收件人拆分和 user updater 投递。
  2. 新增总容量 4096 的有界异步推送队列和 4 个固定分片 worker。同一 tenant + topic 固定进入同一分片,保证同一群的推送和未读更新顺序;不同群可并行处理。
  3. 离线成员 presence 使用一次 Redis pipeline 批量读取,单条消息的 Redis 网络往返由成员数 N 次降为 1 次。
  4. 群离线 presence 遇到 Topic server queue 满时不再直接丢弃。服务端按 tenant + topic + source user 合并为最新状态,并在 Topic 队列恢复后重投;待处理集合上限 16384。
  5. 新增 Topic client queue wait、push prepare、push dispatch queue、push dispatch duration、push recipient count、deferred presence 当前深度等指标。

新增重点指标:

  • im_topic_queue_wait_duration_seconds
  • im_message_publish_stage_duration_seconds{stage="push_prepare"}
  • im_push_dispatch_queue_enqueue_total
  • im_push_dispatch_queue_depth
  • im_push_dispatch_queue_wait_duration_seconds
  • im_push_dispatch_duration_seconds
  • im_push_recipient_count
  • im_internal_queue_current{queue="topic_presence_pending"}
  • im_internal_queue_enqueue_total{queue="topic_presence_deferred"}

发版后先复测精确 600 人群,确认基线无回归;再复测精确 900 人群。900 人验收要求:900/900 ACK、810000/810000 实时投递、0 断连、0 持久化缺失,topic_client/topic_server/push_dispatch 无 full,Topic queue wait P95 不超过 500ms,ACK P95 不超过 500ms,投递 P95 不超过 1s。渐进下线结束后 deferred presence 必须回落到 0,且不得出现 topic_server full。