9.4 KiB
GROUP-2000 R12 双机群聊复测分析
1. 结论
group2k-r12-20261004-01 判定为 失败,2000 人群聊容量仍未通过。
本轮有一个明确的正向结果:R11 后的 Kafka 部分批次修复已经生效。服务端实际接收并可从投递或历史中确认的 1694 条消息,MySQL 精确找到 1694 条,缺失、错配、重复和额外记录均为 0。
实时链路仍然失败。A/B 虽然均以 1000/1000 条连接起跑,但负载开始约 33 秒后公网 /metrics 开始连续超时,随后出现两波大规模 WebSocket reset。最终实时到达率只有 74.739%,ACK P95 为 74.72 秒,包含迟到 ACK 的控制响应 P95 达 407.90 秒。
与 R11 不同,本轮 Pod 没有重启,日志也没有再次出现 CoreDNS 超时。证据表明本轮先发生群 Topic 串行处理积压和入口响应失速,连接被成批 reset 后又触发重连风暴,三个 Pod 的业务库连接池均出现约 9,200-11,350 秒累计等待,最终形成放大回路。
2. 核心结果
| 指标 | R12 结果 | 判定 |
|---|---|---|
| WS 起跑就绪 | 2000/2000 | 通过 |
| A/B 起跑差 | 约 33ms | 通过 |
| 发送尝试 | 2000 | A/B 各 1000 |
| 最终明确 ACK | 1501 | 75.05%,失败 |
| 发送结果未知 | 499 | 失败 |
| 服务端接收/回查确认消息 | 1694 | 另有 306 条未确认进入服务端 |
| ACK P95 | 74.72s | 失败 |
| 含迟到 ACK 的控制响应 P95 | 407.90s | 失败 |
| 期望实时投递 | 3,388,000 | 1694 x 2000 |
| 实际实时投递 | 2,532,160 | 到达率 74.739% |
| 实时缺失 | 855,840 | 丢失率 25.261% |
| MySQL 精确核验 | 1694/1694 | 通过 |
| 重复 / 乱序 | 0 / 0 | 已收到部分正确 |
| WS 断连 | 1257 | 失败 |
| Pod 重启 | 0 | 无应用容器重启 |
publish_final_accepted=1501 只统计客户端最终观察到的 ACK。数据库和消息台账共同证明实际有 1694 条进入服务端,因此其中 193 条属于客户端未拿到 ACK、但服务端已经处理的消息。
3. A/B 分机结果
| 指标 | 机器 A | 机器 B |
|---|---|---|
| 起跑连接 | 1000 | 1000 |
| 发送尝试 | 1000 | 1000 |
| 及时 ACK | 547 | 601 |
| 迟到 ACK | 327 | 26 |
| 结果未知 | 126 | 373 |
| 收到群消息 | 1,389,624 | 1,142,536 |
| 实时缺失 | 304,376 | 551,464 |
| 断连 | 433 | 824 |
| 主要断连原因 | reset 413、timeout 20 | abnormal close 405、reset 419 |
B 在第一波连接重置中受影响更重;第二波则同时影响 A/B,不能归因于单台压测机。
4. 故障时间线
时间均为 UTC,北京时间加 8 小时:
- 15:57:31:A/B 同步起跑,2000 个连接全部就绪。
- 15:57:34-15:57:41:公网
/metrics已从亚秒上升到约 2.5 秒。 - 15:58:04:公网
/metrics首次 10 秒超时;此后连续 28 次超时。 - 15:58:24-15:58:59:第一波约 394 条服务端连接集中关闭,主要对应 B;B 汇总为 405 条 abnormal close。
- 16:00:15 起:
im-server-0开始出现用户缓存更新队列满,窗口内共 78 次。 - 16:01:50-16:02:10:第二波约 810 条服务端连接集中关闭;A/B 汇总的 reset 合计为 832 条。
- 16:02:06 起:大量重连登录出现
context deadline exceeded。 - 16:02:08:
im-server-1readiness 请求本身超时;期间还出现一次 liveness 超时,但未达到重启阈值。 - 16:02:39-16:02:47:三个 Pod 均产生 readiness 503 事件。
- 16:02:48、16:02:50:
im-server-2、im-server-1恢复 Ready;im-server-0未形成持续 NotReady。 - 16:05:04:公网
/metrics恢复 200,首次恢复请求仍耗时约 4.6 秒。 - 16:11:55:A/B 汇总完成。
三个 Pod 的 restartCount 均为 0。R11 出现过的 10.43.0.10:53、no such host、DNS connection refused 在本轮窗口内均未出现,因此不能继续把本轮失败归因于 CoreDNS。
5. 容量瓶颈
群 Topic master 位于 im-server-2。最终指标显示:
| 指标 | 数值 |
|---|---|
| 群发布处理次数 | 1694 |
| Topic publish 处理总耗时 | 116.59s |
| 单条平均处理耗时 | 约 68.8ms |
| Topic 队列等待总耗时 | 2279.92s |
| 单条平均排队 | 约 1.35s |
| 队列等待最大桶 | 30s 内 |
| master 广播总耗时 / 次数 | 21.33s / 4803 |
| master 单次广播平均耗时 | 约 4.4ms |
按 68.8ms 的串行 Topic 处理耗时估算,单群持续处理能力约为 14.5 条/秒,低于本轮约 20 条/秒的目标发送速率。共享序列化后的广播本身仍然只有毫秒级,不是主要瓶颈;耗时主要位于 Topic publish 的保存、Kafka produce、权限检查和推送准备等关键路径。
这也解释了为何在正式断连前 ACK 已经出现 20-90 秒长尾,并且 /metrics 先失去响应。
6. 断连与入口
Pod 日志中的两波连接关闭均来自入口上游连接;客户端看到的是三个源站 IP 返回 connection reset by peer,不是主动退出压测程序:
- 第一波关闭连接的服务端会话寿命平均约 122 秒,集中影响 B。
- 第二波同时影响 A/B;旧连接寿命主要集中在约 330 秒和 660 秒,说明它是同一时刻的批量重置,而非每条连接独立超时。
- 三个源 IP 最终汇聚到同一个入口上游地址的现象仍然存在,入口层缺少副本、连接上限、reload/restart、upstream reset 和节点 conntrack 证据。
应用负载先失速,入口随后批量 reset,因此当前不能只改客户端重连参数来宣告通过。运维仍需补齐 Traefik、入口节点和 Service endpoint 的同窗口证据,判断 reset 是入口进程、节点网络、连接上限还是上游超时策略触发。
7. 重连放大与数据库
压测前后业务库连接池增量如下:
| Pod | 新增等待次数 | 新增累计等待 | 平均每次等待 |
|---|---|---|---|
| im-server-0 | 817 | 9956.29s | 约 12.2s |
| im-server-1 | 770 | 9218.27s | 约 12.0s |
| im-server-2 | 768 | 11348.20s | 约 14.8s |
每个 Pod 的 meeting_im 最大连接数为 24。连接集中断开后,压测端自动重连触发登录、用户状态、IP 白名单和认证版本查询,连接池等待迅速升高;登录进一步超时,readiness 中的数据库检查也随之失败,Pod 被入口摘除,形成反馈回路。
不能简单地只提高连接池上限。下一轮前应先定位连接被长时间占用的 SQL/事务,并为登录重连增加服务端限流、排队和抖动;否则只是把压力转移到 MySQL。
8. Kafka 与落库
数据库直连核验结果:
| 项目 | 结果 |
|---|---|
| tenant | allrich / tenant_id=8 |
| 期望消息 | 1694 |
| 精确找到 | 1694 |
| 缺失 | 0 |
| 字段错配 | 0 |
| 重复行 | 0 |
| 额外记录 | 0 |
| 分表 | im_008_group_messages_202610、im_008_messages |
这证明 R11 的 Kafka 部分批次丢弃问题已修复。最后一次公网指标采样中某个 reader 的本地 im_kafka_consumer_lag 仍显示 111,但稍后的数据库核验已经完整;再次说明 kafka-go Reader.Stats().Lag 不能作为 consumer group 已提交 offset 的最终依据。
9. 改进优先级
P0:群 Topic 关键路径
- 对 Topic publish 的 save、Kafka produce、权限查询、推送准备分别补充 P95/P99 和并发占用,确认 68.8ms 的主要组成。
- 将不要求阻塞 ACK 的推送准备、通知类工作移出 Topic 单写者关键路径。
- 对必须保持顺序的持久化和序列分配设计有界流水线;保持同群顺序和失败可重试,不能重新引入已移除的业务 outbox。
- 优化后先跑 10、12、15、18、20 msg/s 阶梯,连续两轮确认 Topic 队列不持续增长,再重新判定 20 msg/s 容量。
P0:入口批量 reset
- 由运维提取 15:57:30-16:05:10 的 Traefik Pod 日志、事件、restart、reload、upstream reset 和打开连接数。
- 核对三个公网源 IP 是否最终只有一个 Traefik 实例承载;入口至少配置两个副本、反亲和和 PDB。
- 同步采集节点 conntrack、TCP retransmit、listen overflow、文件描述符、网卡丢包和 CPU throttling。
- 下一轮同时探测公网域名、每个源 IP、Service 和三个 Pod,避免
/metrics单点失明。
P1:重连保护
- 客户端维持自动重连,但使用指数退避和随机抖动,避免 2000 条连接同步登录。
- 服务端对登录重建增加有界并发和快速过载响应,不让请求无限占用数据库连接池。
- 分解登录 SQL 指标,定位连接池长等待对应的查询或事务。
- readiness 的数据库检查应使用独立的小连接预算或短路缓存,避免业务池耗尽导致全部 Endpoint 同时被摘除;liveness 继续只检查进程存活。
10. 下一轮顺序
- 先做 2000 连接、0 发消息的 15 分钟稳定性测试,排除入口固定连接寿命和 B 网络路径问题。
- 再做单群阶梯:10、12、15、18、20 msg/s,每档 5 分钟,观察 Topic queue 是否线性增长。
- 完成入口和数据库现场采集后,重复 R12 原参数两轮。
- 通过标准:2000/2000 连接全程稳定、发送成功率不低于 99.99%、实时到达率不低于 99.99%、DB 精确落库、无批量 reset、无持续 Topic/DB 队列增长。
11. 产物
- 结果目录:
loadtest/im-pressure/results/group2k-r12-20261004-01/ - 汇总:
report.json - 数据库核验:
db-verification.json - A/B 明细:
agent-a-summary.json、agent-b-summary.json - 协调器台账:
coordinator.json - 指标:
metrics.jsonl - 被测代码:
965f94f fix(kafka): preserve partially fetched message batches