IM千人群聊该不该用消息写扩散?详细算算成本账!

本文作者拉丁解牛说技术,有修订和改动。

1、引言

本文将为你详解 IM 群聊消息的写扩散技术原理以及写扩散过程中的服务端详细性能成本,同时对比了市面上IM大厂的技术方案,给出分规模架构选型与以及性能优化策略等。

cover-opti

2、技术背景

某天下午,你在一个 1000 人的项目群里发了一条 7 个字的消息:「今晚九点发版」。对你来说,这是一次点击、一段 7 字符的字符串、一次"已发送"的小对勾。

但服务端为这条消息做了什么?

按写扩散(Fan-out on Write)的实现思路,最直观的版本是这样的:

0

仅消息副本本身就是 999 次扇出。加上路由查询、未读写入、三方推送触发,一条 7 字消息在服务端产生 3000~5000 次后端操作不是夸张。

这就是"写扩散"这个词背后的真实账单。

1

▲ 群消息的扇出位置(群服务是「1 → N」的关键)

3、群聊消息为什么要做扇出?

群消息这个功能在工程上有两条出路:

2

中小 toB 项目大多选写扩散,因为 toB 场景里"已读回执 / 消息撤回 / 离线补全"是硬需求 — 没有每成员维度的"投递记录",这三个功能都立不住。

4、中小规横IM项目的典型设计目标

中小规模 toB IM 项目里,群消息扇出这一层通常要同时满足:

  • 1)实时性:在线成员从发送到收到 P95 < 100ms;
  • 2)可靠性:离线成员上线后必须能补到这条消息;
  • 3)可观测:每个副本的投递状态可追踪(用于 SRE 排障);
  • 4)业务能力:支撑已读回执、消息撤回、漫游、未读统计;
  • 5)规模:单群默认 1000 人,少数业务场景 3000~5000 人。

这些约束串起来,几乎一定走向写扩散。

5、写扩散的消息副本生成时机

候选 3 种:

3

主流选择:群服务展开。

理由是:责任边界清晰 — 群成员、群属性、群配置全在群服务,扇出本来就应该是它的活。

伪代码大致是这样:

on_group_message_arrive(msg):

    group = group_cache.load(msg.group_id)

    members = group.member_list # 1000 人

    members = filter_out_sender(members, msg.from) # 999 人

    for member in members:

        copy = clone_with_target(msg, member.uid)

        copy.uuid = msg.uuid + "|G" + member.short_id // 副本标识

        publish_to_dispatcher(copy)

注意最后那个 uuid + "|G" + member.short_id — 这是写扩散里的"副本指纹",下游所有组件(历史库、离线、已读聚合)都靠这个区分"原消息"和"副本"。

6、扇出执行方式

这是最容易踩坑的设计点。3 种执行方式的对比:

4

中小项目早期常见的姿势是异步 MQ 逐条 — 简单、容易实现、文档好查。

但当群规模到 1000 人级别时,MQ 写入次数会成为系统瓶颈:

单条 MQ 写入耗时 ~1~3ms,999 条串行 ≈ 1~3 秒;并行能压到 100ms 内,但 broker 压力大

如果有 100 个千人群同时活跃,MQ broker 每秒要接收 ~10 万条扇出消息

改进方向:MQ 批量化。

把 999 个目标用户分成 20 批,每批 50 人装在一条 MQ 消息里:

on_group_message_arrive(msg):

    members = filter_out_sender(group.member_list, msg.from)

    for batch in chunk(members, batch_size=50): // 50 人一批

        batch_msg = {

            "origin": msg,

            "targets": [m.uid for m in batch]

        }

        publish_to_dispatcher(batch_msg) // 一次 MQ 写

下游分发服务消费时再"展开"批次,对批内每个目标做路由 + 投递。MQ 写次数从 999 降到 ~20,吞吐能力提升一个数量级。

7、副本指纹与双轨存储

写扩散的一个隐蔽问题是:副本要不要进历史库?

该怎么选?

  • 1)进:千人群 1 条消息进历史库 999 条,分表空间爆炸
  • 2)不进:那历史消息只存原消息,未读 / 已读怎么对账?

主流做法是双轨:

5

▲ 写扩散的双轨存储:历史库只有 1 条,离线盒子可能有 N 条

判断规则:

6

历史库消费者扫一眼 uuid 里有没有 |G 就知道丢弃还是入库。这是工程上极简但关键的判断。

8、写扩散场景下的失败处理

写扩散场景下,"完全成功"是个奢侈品。需要承认部分失败常态,设计要点如下。

1)失败必须可重试:副本本身就是 MQ 消息,失败 = 不 ACK,broker 自动重投。

2)重试要有上限:重试 3~5 次仍失败的进死信队列,由 SRE 人工处理。

3)不让发送方感知:发送方在原消息入库时就 ACK 了("消息已发送"),扇出失败是后台事故,不回传到发送方。

4)失败可定位:每个副本带原消息 uuid + 目标 uid,死信里能精确知道"哪条消息没投到哪个人"。

伪代码:

on_dispatcher_consume(copy_msg):

    try:

        route_and_deliver(copy_msg)

    except RetryableError as e:

        if copy_msg.retry_count < 5:

            reject_and_requeue(copy_msg) // MQ 重投

        else:

            send_to_dead_letter_queue(copy_msg) // 死信,SRE 处理

            metrics.inc("group.fanout.dead_letter")

    except FatalError as e:

        send_to_dead_letter_queue(copy_msg)

9、按群规模进行分档

写扩散有个无法跨越的物理上限:群越大,扇出代价越接近不可承受。

具体是:

  • 1)100 人群:100 次副本,舒服;
  • 2)1000 人群:999 次副本,已经要做批量化;
  • 3)5000 人群:4999 次副本,需要专门优化策略;
  • 4)10000 人群:写扩散根本走不通。

工程上必须明确一条线:超过 N 人的群切换策略。

主流做法:

7

10、群消息扇出的时序流程

把上面 5 个设计点串起来:

8

▲ 群消息扇出的完整时序

11、主流大厂的大群消息投递方案

9

不同体量的 IM 系统在这一层的设计差异极大。写扩散不是唯一解 — 当规模到某个临界点,读扩散反而是更合理的选择。

11.1 某钉IM

钉IM,最早也走写扩散,但在万人群场景下扛不住,演进为读扩散为主 + 推拉结合 + 多级缓存。(相关资料请详读《深度解密钉钉即时消息服务DTIM的技术设计》一文中的“4.3、存储模型设计”章节)

核心思路:

  • 1)群消息只存一份(不再为每个成员复制);
  • 2)成员"读群"时按订阅关系拉取;
  • 3)实时性靠"近期消息推送 + 历史消息客户端按需拉取"补足;
  • 4)多级缓存(万人群成员缓存)让"反查群成员"快到毫秒级。

10

▲ 最终:他们用智能限流和万人群成员多级缓存来支撑超级大群的运营

11.2 某信IM架构

在群消息"存一份还是存多份"的存储架构权衡上,相关高级研发工程师在技术博客分享中提到,采用写扩散路线(详见《微信直播聊天室单房间1500万在线的消息架构演进之》一文中的“5、消息扩散方案选型:读扩散”章节)。

即每条群消息按成员数复制成多份存储,换取读路径的极致简单(撤回、漫游、未读计数都只需操作单成员的消息流)。

代价是写放大与存储成本随群规模线性上涨。

11

11.3 企某信的IM架构

企某信和总所周知的微信,是两个系统,企某信的设计目标是支持万人群 + 已读回执。

它的做法是:仍然走写扩散,但投入更多硬件资源 + 各种局部优化(批量、缓存、异步)来支撑万人群的扇出成本。(相关资料可详读《企业微信的IM架构设计揭秘》一文中的“6、整体架构设计3:消息扩散写”章节)

12

11.4 总结对比

13

12、性能优化思考

中小规模 toB IM 在群扇出这一层有一条清晰的演进路径 — 但具体怎么走,要从你的产品定位、规模分布、工程团队能力倒推。下面是几个值得反复思考的点。

1)群规模上限:你的产品定位决定了上限的硬约束。如果业务场景就是大企业、需要万人群(如政府客户、大型集团内部沟通),那从 day 1 就应该认真评估读扩散方案 — 等到业务跑起来再切,技术债的复利会让人后悔。但如果只是一个中小企业协作工具,500 人甚至 1000 人上限其实没问题(参考微信群限制 500 人的判断)。先决定群规模的产品上限,再决定架构 — 这个顺序反过来就麻烦。

2)MQ 批量化:观察过的几乎所有中小规模 toB IM 项目都在 MQ broker 上付过血泪学费。Broker 的瓶颈往往不是磁盘 IO,而是连接数和单 partition 的串行化处理。批量化(batch=20~50)能让 MQ 写入次数降一个数量级,是收益明显的优化方向。但要注意批量化引入的复杂性 — 一批 50 个目标,其中 3 个失败该怎么处理?个人在中等规模 IM 项目里观察到的常见做法是"批内失败拆分重投"(把失败的 3 个重新打包成新的小批次发回 MQ)。

3)副本瘦身:副本带不带完整 payload 是个分歧点。带 payload 简单(下游消费者不需要反查原消息)但浪费带宽(千人群 1 条 1KB 消息 = 1MB 流量);不带 payload 省带宽但下游消费者要做一次 KV 反查。比"完全瘦身"更工程化的做法是分档:消息体 < 1KB 时带 payload;超过就拆 — 副本只带 uid + uuid + 摘要(首 100 字符做消息预览),下游展开时按 uuid 反查全文。这种"内嵌摘要 + 按需反查"的混合方案,既能保留消息列表的预览体验,又能控制带宽峰值。

4)可观测体系:很多中小项目的扇出层处于"出问题时翻日志、平时不看"的状态,这是个隐患 — 当你发现"群消息延迟变高"时,往往已经在客服工单里出现了。

最值得加的三个指标分别是:

  • a)单条群消息的扇出 RT P99(衡量端到端时长,是不是有慢扇出);
  • b)副本投递失败率按时间窗口分桶(衡量是否有阶段性故障);
  • c)批内部分失败率(如果做了批量化,衡量批是不是被"少数失败拖累整批")。

这三个指标加起来,P0 故障定位时间能从"翻半小时日志"压缩到"看一眼大盘"。

5)大群临界点:如果业务真的开始需要 5000 人以上的群,参考某钉从写扩散切到读扩散的演进经验,关键经验是 — 切换不是一蹴而就的。中间过渡期通常是混合模式:千人以下继续写扩散(保留已读 / 撤回的低成本实现),千人以上的群单独走读扩散通道(客户端读消息时按 uuid 范围拉取,已读用 bitmap 聚合)。这种"按规模分流"的混合策略可以让团队渐进切换,不需要一刀切的大重构。关键是分流的判断点要明确(在群创建时就标记类型),而不是在每次消息扇出时动态判断 — 后者会让代码路径越来越复杂。

写扩散不是终点,是某个规模下的最优解。真正的工程能力,是知道什么时候该走,什么时候该停。

13、参考资料

[1] IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

[2] IM群聊消息如此复杂,如何保证不丢不重?

[3] 移动端IM中大规模群消息的推送如何保证效率、实时性?

[4] 现代IM系统中聊天消息的同步和存储方案探讨

[5] 关于IM即时通讯群聊消息的乱序问题讨论

[6] IM群聊消息的已读回执功能该怎么实现?

[7] IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?

[8] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

[10] IM群聊机制,除了循环去发消息还有什么方式?如何优化?

[11] 云信技术分享:IM中的万人群聊技术方案实践总结

[13] IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

[15] 企微的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

[16] 融云IM技术分享:万人群聊消息投递方案的思考和实践

[18] 海量用户IM聊天室的架构设计与实践

[20] 支持百万人超大群聊的Web端IM架构设计与实践

[21] IM千人群聊该不该用消息写扩散?详细算算成本账!

即时通讯技术学习:

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)

(本文同步发布于: http://www.52im.net/thread-4915-1-1.html)

相关推荐

鸿蒙原生即时通讯/IM系统RainbowTalk v2.8版发布,基于MobileIMSDK框架、ArkTS编写

💧 此版本解决了若干bug,以及ui和功能细节优化,同时支持最新鸿蒙7.0系统。

hellojackjiang2011的博客 163

深度剖析IM消息乱序的根本原因及优化方法

本文深度剖析即时通讯IM消息乱序的问题,梳理IM客户端时间戳不可信、多端并发、群消息扇出重试三大核心陷阱,拆解微信、钉钉、企业微信的时序设计方案,并从锚点设计、多端去交错、幂等兜底、可观测性等维度,给出IM项目消息时序性保证的落地优化策略。

hellojackjiang2011的博客 293

IM聊天不掉线?生产级WebSocket断线重连、心跳保活实战

本文将给出一套生产级Web端即时通信方案,包括心跳检测保活 + 指数退避重连,代码直接能跑。

hellojackjiang2011的博客 314

IM全文检索技术(五):vivo敏感词匹配系统的技术实践

本文将从算法选型入手,结合两个实际场景来介绍谛听系统的敏感词匹配算法。包括底层算法选型思路和两种实际场景下提升敏感词精准度、降低漏杀率的方案。

hellojackjiang2011的博客 189

Nginx生产级WebSocket实时长连接负载均衡,一文搞定

本文从基础配置、生产配置、Docker / K8s、安全、排障、性能调优,系统性讲清 WebSocket 在 Nginx 中的正确打开方式。

hellojackjiang2011的博客 308

即时通讯IM离线消息该怎么存?全面盘点主流存储方案

离线消息是IM系统的核心能力,当用户不在线时,消息必须可靠存储,待用户上线后完整推送。本文将对比主流的离线消息存储方案,帮你找到最适合业务场景的架构。

hellojackjiang2011的博客 310

移动端实时音视频聊天框架 RainbowAV v2.2.7发布

1)

hellojackjiang2011的博客 247

都HTML5了,Web端即时通讯技术到底该用什么?一文即懂!

本文分享的是 Web 端最常用即时通讯技术(包括 WebSocket、SSE、WebRTC、轮询等)的原理、优缺点与适用场景等,以及生产环境下的高并发场景下的进阶实践。

hellojackjiang2011的博客 341

基于开源MobileIMSDK框架,即时通讯IM产品RainbowChat v12.1发布

此版本新增了短信验证码的注册和登录功能,解决了Android 16手机上发送文件失败等问题。

hellojackjiang2011的博客 209

直播系统聊天技术(十):基于实时消息总线的活动系统架构设计

本文分享的是网易云音乐技术团队基于实时消息总线技术,解决了直播活动系统的模块灵活组合、消息治理与异步履约等问题,希望能给你带来启发。

hellojackjiang2011的博客 369

轻量级Web端IM即时通讯框架MobileIMSDK-Web,基于Socket.io库

MobileIMSDK-Web是一套Web端即时通讯框架,超轻量级、极少依赖,高度提炼、简单易用。

hellojackjiang2011的博客 286

socket长连接在手游场景下的技术实践

本文介绍了37手游基于B站goim框架自研长连接系统的实践。系统采用分层设计,支持多协议和发布/订阅机制,用于直播弹幕、实时推送等场景,实现了高性能与业务适配。

hellojackjiang2011的博客 403

基于开源MobileIMSDK框架,即时通讯IM产品RainbowChat v12.0发布

RainbowChat 是一套基于 MobileIMSDK 开源通信框架的产品级移动端IM系统。可能是市面上开放源码的,唯一一款同时支持TCP、UDP、WebSocket三种网络通信协议的IM产品。

hellojackjiang2011的博客 518

社交场景下的统一即时通讯im消息流交互层模块化技术实践

这些IM场景,在消息流的展示形式上是极为相似的,同时每个业务又有着自己特殊的交互需求。基于此,我们对IM消息流能力做了标准化的构建,来减少IM功能的业务接入成本;同时也是为了统一各个业务的技术方案,减少跨业务开发的理解和维护成本。

hellojackjiang2011的博客 571

现代IM系统中聊天消息的同步和存储方案探讨

本文内容主要涉及IM系统中的消息系统架构,探讨一种适用于大用户量的消息同步以及存储系统的架构实现,能够支持消息系统中的高级特性“多端同步”以及“消息漫游”。在性能和规模上,能够做到全量消息云端存储,百万TPS以及毫秒级延迟的消息同步能力。

hellojackjiang2011的博客 75

即时通讯安全篇(十六):对称加密 vs 非对称加密?一文搞懂

本文将以通俗案例讲解加密本质,介绍对称加密(含 AES、迪菲–赫尔曼密钥交换)与非对称加密(RSA)原理、特点及应用,并阐释其数学基础。

hellojackjiang2011的博客 397

网络编程入门如此简单(五):UDP跟TCP相比,到底差了什么?

本文将通俗易懂地为你类比解释UDP与TCP的核心差异,包括如何基于UDP实现TCP的可靠传输:通过模拟三次握手、添加序列号与确认机制解决顺序和丢包问题,利用滑动窗口控制流量,并引入拥塞控制算法来动态调整发送速率等。

hellojackjiang2011的博客 448

基于WebSocket的IM即时通信方案在H5游戏场景下的技术实践

本文将从H5游戏场景下的实时信息更新需求切入,探讨下在类似这种高并发、低延迟的业务需求中,如何使用 IM 方案来解决频繁的数据更新问题,也顺便介绍下 WebSocket 的基本运作流程等。

hellojackjiang2011的博客 1069

鸿蒙Next原生IM即时通讯RainbowTalk,纯ArkTS编写,基于开源MobileIMSDK框架

RainbowTalk 是一套基于 MobileIMSDK 开源通信框架的产品级纯血鸿蒙NEXT端IM系统。RainbowTalk 由纯ArkTS编写、全新开发,没有套壳、也没走捷径,原生“纯血”。

hellojackjiang2011的博客 1227

如何保障分布式IM聊天系统的消息可靠性(即消息不丢)

本文主要聚焦分布式IM聊天系统消息可靠性问题,即如何保证消息不丢失。

hellojackjiang2011的博客 732

如何保障分布式IM聊天系统的消息有序性(即消息不乱)

本篇主要总结和分享分布式IM聊天系统架构中关于消息有序性的设计和实践。

hellojackjiang2011的博客 835

B站即时通讯IM消息系统的新架构升级实践

本文要分享的是B站IM消息系统的新架构升级实践总结,内容包括原架构的问题分析,新架构的整体设计以及具体的升级实现等。

hellojackjiang2011的博客 714

AI大模型爆火的SSE技术到底是什么?万字长文,一篇读懂SSE!

本文从SSE(Server-Sent Events)技术的原理到示例代码,为你通俗易懂的讲解SSE技术的方方面面。

hellojackjiang2011的博客 786

腾讯新闻APP的消息推送Push架构技术重构实践

本文主要分享的是腾讯技术团队近年来对腾讯新闻消息推送PUSH系统做的架构优化和技术实践。

hellojackjiang2011的博客 843

移动端弱网优化专题(十五):字节跳动移动端网络HttpDNS优化实践

本文要分享的是字节跳动团队针对火山HTTPDNS Cache2.0通过自研网段库与动态划分算法,将缓存粒度从“城市-运营商”细化为“网段”,解决了传统方案的城市级调度污染问题。配合缓存分级、预取等优化,在提升调度精准度的同时保证了高命中率,最终实现了服务端调度准确性提升和客户端性能优化。

hellojackjiang2011的博客 720

转转客服IM聊天系统背后的技术挑战和实践分享

在当今互联网时代,高效的用户服务是提升用户体验的关键。转转自研的客服IM聊天系统作为用户与客服沟通的桥梁,承担着传递信息、解决问题的关键角色。然而,消息数据的流转并非一帆风顺,本文将深入探讨IM系统在消息传递过程中遇到的问题和挑战,以及相应的技术解决方案。

hellojackjiang2011的博客 1096

ChatGPT如何实现聊天一样的实时交互?快速读懂SSE实时“推”技术

本文将带你快速认识SSE实时通信协议,包括它的技术原理、常见使用场景、与同类技术的对比以及简单的示例代码等。

hellojackjiang2011的博客 124

全面适配iOS 26液态玻璃,基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v10.2发布

RainbowChat是一套基于开源IM即时通讯聊天框架 MobileIMSDK 的产品级移动端IM系统。

hellojackjiang2011的博客 1157

可靠传输的TCP协议send成功就意味着数据一定发出去了?

本篇文章我们以TCP协议的网络编程逻辑,从Socket缓冲区的角度去拆解,为什么号称可靠传输的TCP协议,在代码中调用send并成功发出数据,并不意味着这个数据就一定通过物理网络发出去了。

hellojackjiang2011的博客 125

即时通讯安全篇(三):一文读懂常用加解密算法与网络通讯安全

作为开发者,也会经常遇到用户对数据安全的需求,当我们碰到了这些需求后如何解决,如何何种方式保证数据安全,哪种方式最有效,这些问题经常困惑着我们。52im社区本次着重整理了常见的通讯安全问题和加解密算法知识与即时通讯/IM开发同行们一起分享和学习。

hellojackjiang2011的博客 121
上一篇: 直播系统聊天技术(十):基于实时消息总线的活动系统架构设计
下一篇: 基于开源MobileIMSDK框架,即时通讯IM产品RainbowChat v12.1发布
jackjiang2025
jackjiang2025 网际时代技术分享账号 网际时代技术分享账号
博客等级 码龄8年 700粉丝 318原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值