perf: batch group unread fanout

This commit is contained in:
richjohnrambo
2026-09-27 23:10:28 +08:00
parent fc875cc84e
commit 6b8ed4a2cc
10 changed files with 260 additions and 4 deletions
@@ -105,3 +105,41 @@ Run ID:`group2k-r1-20260927-01`
- 实时广播:失败,存在队列丢弃和断线。
- Kafka 持久化:已处理的 488 条全部成功,但该结论不代表计划的 2000 条均被接受。
- 总体:失败,必须完成 P0 后按阶梯重测。
## 7. P0 修复实现
本轮分析后的代码修复如下:
1. 群消息的 `{pres what=msg}` 不再由 Topic master 对每个成员逐条跨 Pod 路由。master 将收件人清单合并为一条 `group_unread/users` Redis Pub/Sub 消息;来源 Pod 本地投递一次,其他 Pod 各消费一次。
2. 每个 Pod 只扫描一次本地 WebSocket:已订阅该群 Topic 的连接依靠正常 `{data}` 广播,不重复发送 `me` presence;同一用户未订阅该群的其他设备仍收到 `me` presence,保留多开语义。
3. Redis fanout 未启用或批量发布失败时,自动回退到原来的逐用户 Topic 路由,避免静默漏掉未读通知。
4. 原生 Tinode Cluster 开启时,普通 user message 不再重复发布 Redis user fanout;session control 仍保留 Redis 跨 Pod 广播。
5. `routeSrv` 改为有界非阻塞写入,队列满时记录错误和指标,不再无限阻塞 Topic 处理链路。
6. 新增 `im_internal_queue_enqueue_total`、`im_internal_queue_depth`、`im_internal_queue_capacity`,覆盖 `session_topic_publish`、`hub_route_server`、`topic_client`、`topic_server`、`cluster_topic_proxy`。
修复后的复杂度从“每条消息约 2000 次 Cluster route + 2000 次 Redis publish”降为“一次 Redis publish + 每 Pod 一次本地 session 扫描”。2000 人都订阅群 Topic 时,批量 unread 在本地扫描后不会产生重复 WebSocket 帧。
## 8. 复测观测项
复测继续使用 `group2k-r1` 相同发送模型,并重点检查:
```promql
sum by (queue, result) (rate(im_internal_queue_enqueue_total[1m]))
histogram_quantile(
0.95,
sum by (le, queue) (rate(im_internal_queue_depth_bucket[1m]))
)
sum by (stage, kind, target_type, status) (
increase(realtime_fanout_events_total[5m])
)
```
验收要求:
- `session_topic_publish`、`topic_client`、`topic_server`、`cluster_topic_proxy` 的 `result="full"` 均为 0。
- `group_unread/users` 的 publish 数应接近服务端实际接受的群消息数,不再乘以群成员数。
- 群压测窗口内普通 `message/user` Redis publish 不再随群成员数增长。
- `im_cluster_route_total` 不应再出现每条群消息约 2000 次的线性放大。
- 2000 条计划消息需分别核对 ACK、未知结果、实时观测和最终落库,不能只按 ACK 台账判断落库率。