Files
im_server/docs/test/IM-group2k-r9-双机群聊压测分析与改进-20261003.md
T

74 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GROUP-2000 R9 双机群聊压测分析与改进
## 1. 结论
`group2k-r9-20261003-01` 不能作为 2000 人群聊容量结论。消息最终落库完整,但实时阶段同时发生机器 B 迟到、Pod 重启和集群 DNS 拒绝查询,导致大量连接断开和跨机器投递缺失。
- 发送尝试 2000 条,数据库精确找到 1774 条服务端已确认消息,缺失、重复、字段不一致均为 0。
- 实时期望投递 3,548,000 次,实际收到 1,384,872 次,到达率约 39.03%。
- 机器 B 比 coordinator 的计划起跑时间晚约 7 分 55 秒;B 未收到任何 A 消息,本轮并非有效的同步双机负载。
- `im-server-1` 在负载中退出,容器退出码 137;现有权限无法读取 Event 和 Pod metrics,尚不能把原因唯一归为 OOM、liveness 或节点压力。
- 三个 Pod 合计约 1715 次依赖域名解析失败,日志包含对集群 DNS `10.43.0.10:53` 的 `connection refused`。
- JPush 设备查询产生 792 次数据库错误;用户更新队列满 1132 次,属于依赖抖动和连接重建后的次生压力。
Kafka 持久化链路本轮表现正确:各 Pod 批量持久化计数合计 1774,与数据库精确核验一致,之前的失败消息保留修复已生效。
## 2. 根因链路
### 2.1 JPush 查询按群消息放大
JPush 使用 registration ID 模式时,每条群消息都会按全部收件人重新查询 `im_device_push_tokens`。2000 人群和 2000 条消息会形成约 400 万个用户查询输入;测试账号没有推送设备,旧实现仍重复查询空结果。JPush 回执又以无限 goroutine 并发处理,使查询、对象分配和内存压力同时放大。
### 2.2 MySQL 连接每 60 秒回收
旧配置的 `conn_max_lifetime=60` 会在持续压测期间频繁重建连接。每次建连都重新解析 `mysql.118im.com`;当集群 DNS 短时拒绝请求时,业务查询、JPush 查询和 Kafka 持久化会一起失败或等待。
### 2.3 双机只有计划时间,没有最终到达屏障
旧协议在双方 READY 后下发统一时间,但 agent 到达该时间后直接发送。机器 B 晚启动时,机器 A 无法知道对方没有真正进入发送阶段,因此形成两段错开的单机流量并生成无效的跨机器统计。
## 3. 已完成改进
### 3.1 JPush
- 设备查询结果按租户、provider、用户缓存 30 秒,包含“无设备”结果。
- 并发首次查询被合并,同一批群成员只访问一次数据库。
- 查询失败后退避 1 秒,已有缓存可在 5 分钟陈旧窗口内继续使用。
- 缓存定期清理,避免用户集合长期增长。
- JPush 回执限制为 8 个工作协程,取消无限 goroutine,队列负责形成背压。
该改动不删除在线多端静默推送语义。新注册或更新的设备最迟约 30 秒进入推送查询结果。
### 3.2 MySQL 与 DNS
- 容器默认 `conn_max_lifetime` 从 60 秒提高到 1800 秒,并支持 `MYSQL_CONN_MAX_LIFETIME_SECONDS` 覆盖。
- 测试环境 GitOps 显式设置 1800 秒,降低持续负载中的连接重建和 DNS 查询频率。
- 保留现有 `ndots:1`、`timeout:2`、`attempts:2`。
这会显著减少应用制造的 DNS 压力,但不能替代集群 DNS 修复。若复测仍出现 `10.43.0.10:53 connection refused`,需要集群管理员检查 CoreDNS 副本、CPU、转发上游、conntrack,并评估与当前 K3s 网络模式匹配的 NodeLocal DNSCache;不能在未知网络模式下直接套用 DaemonSet。
### 3.3 压测同步
- coordinator 增加最终起跑屏障。
- A/B 必须在计划时间前后 3 秒内到达,双方都到达后才开始发送。
- 任一机器迟到时,两边收到同一失败原因并终止本轮。
- coordinator 输出保存双方实际到达时间和 `start_error`。
### 3.4 诊断权限
`backend-debug` 只读权限增加 Event 和 `metrics.k8s.io/pods`,用于下一轮区分 OOM、探针失败、驱逐和节点资源压力。若集群未安装 metrics-server,Pod metrics 仍不可用,但 Event 读取不受影响。
## 4. 复测门禁
下一轮使用新 `run_id`,A/B 必须更新到包含起跑屏障的同一提交,并在应用和 GitOps 配置都发布后执行。
1. coordinator 输出中 A/B `started` 时间相差不超过 3 秒,`start_error` 为空。
2. 2000 条发送全部得到明确 ACK;数据库按 `client_mid` 精确匹配,缺失、重复、字段不一致均为 0。
3. 实时期望 4,000,000 次投递全部到达,重复、乱序和断连均为 0。
4. JPush 设备查询不再随群消息数线性增长,日志中 `jpush: db error` 为 0。
5. Pod 不重启,Event 中无 OOM、Unhealthy、Evicted;负载窗口内持续采集 CPU、内存和重启次数。
6. `10.43.0.10:53`、MySQL、Redis、Kafka 域名解析错误均为 0。
7. Kafka lag 最终归零,数据库最终消息数与 ACK 台账一致。
若业务正确性通过但 DNS 仍有拒绝请求,本轮仍判基础设施门禁失败,不继续用增加应用重试掩盖 DNS 容量问题。