Jack Jiang

我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
posts - 541, comments - 13, trackbacks - 0, articles - 1

2026年6月8日

1、v12.1 版更新内容

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

详细更新内容更多历史更新日志):

  • 1)[新增] 新增了短信验证码的注册和登录功能;
  • 2)[bug] 解决了Android 16手机上发送文件失败的问题(系统不允许读取DATE_MODIFIED字段);
  • 3)[bug] 解决了群聊中查看群公告时不显示编辑者头像的问题;
  • 4)[优化] ui的细节美化和提升(包括图标黑化、标题栏/tab栏白化净化、列表横线净化等等);
  • 5)[优化] 优化了聊天界面中未读消息数的ui;
  • 6)[优化] 个人相册、个人语音、文件查看、视频录制和播放、扫一扫等界面的ui优化;
  • 7)[优化] 陌生人聊天界面标题栏小字被点击后弹出的提示框中增加了“加好友”按钮;
  • 8)[优化] 解决targetSDKVersion=36时,不支持edge to edge全面屏的老旧手机状态栏跟标题栏颜色不一致的问题;
  • 9)[优化] 个人中心增加了手机号的显示;
  • 10)[优化] 视频播放界面增加视频转发功能;
  • 11)[优化] 优化了查看用户头像的缓存加载逻辑;
  • 12)[优化] 其它细节优化和提升;

2、关于MobileIMSDK开源工程

a1-opti

MobileIMSDK 是一套全平台开源IM即时通讯聊天框架,超轻量级、高度提炼,一套API优雅支持UDPTCPWebSocket 三种协议,客户端支持iOS、Android、H5、小程序、Uniapp、标准Java、纯血鸿蒙等,服务端基于Netty编写,性能卓越、易于扩展。

3

工程同步开源地址:

仓库文件目录:

a2-opti

框架详细组成:

a3-opto

3、关于RainbowChat

 1

🔥⚡️ 详细介绍版本日志・运行截图(AndroidiOS)・下载体验(AndroidiOS) ⚡️🔥

RainbowChat是一套基于开源IM即时通讯聊天框架 MobileIMSDK产品级移动端IM系统RainbowChat源于真实运营的产品,解决了大量的屏幕适配、细节优化、机器兼容问题。RainbowChat可能是市面上提供im即时通讯聊天源码的,唯一一款同时支持TCP、UDP、WebSocket三种通信协议的IM产品。与姊妹产品 RainbowTalkRainbowChat-Web 技术同源,历经考验。

4、运行截图

主要功能运行截图:(☞ Android端完整截图iOS端完整截图) 

12_real_voice

13_real_video

27_revoke_e

全部功能拼合大图:(☞ 点此查看清晰原图

v121_all_in_one_75pct_h2000px

5、运行视频

(☞ 新窗口中查看运行视频

动态截图

posted @ 2026-07-16 12:41 Jack Jiang 阅读(29) | 评论 (0)编辑 收藏

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

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

posted @ 2026-06-24 17:18 Jack Jiang 阅读(52) | 评论 (0)编辑 收藏

本文由云音乐技术团队燕十三分享,有修订和改动。

1、引言

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

cover-opti

2、系列文章

本文是系列文章中的第 10 篇:

  1. 直播系统聊天技术(一):百万在线的美拍直播弹幕系统的实时推送技术实践之路
  2. 直播系统聊天技术(二):阿里电商IM消息平台,在群聊、直播场景下的技术实践
  3. 直播系统聊天技术(三):微信直播聊天室单房间1500万在线的消息架构演进之路
  4. 直播系统聊天技术(四):百度直播的海量用户实时消息系统架构演进实践
  5. 直播系统聊天技术(五):微信小游戏直播在Android端的跨进程渲染推流实践
  6. 直播系统聊天技术(六):百万人在线的直播间实时聊天消息分发技术实践
  7. 直播系统聊天技术(七):直播间海量聊天消息的架构设计难点实践
  8. 直播系统聊天技术(八):vivo直播系统中IM消息模块的架构实践
  9. 直播系统聊天技术(九):千万级实时直播弹幕的技术实践
  10. 直播系统聊天技术(十):基于实时消息总线的活动系统架构设计》(* 本文

3、技术背景

所谓组装,就离不开老生常谈的复用,我们可以对大部分认为比较共性的场景做好系统级别的封装,封装成一个个复用度较高的服务,然后通过接口和扩展点的方式进行一部分的能力开放,但是有一种场景是解决不了的,就是当一个功能级别的代码执行结束后,希望触发到另外一个功能,同时希望这个功能是可以通过配置去解决的,并且不需要通过开发的手段去解决这类问题。

例如,用户送了一个礼物给一个主播,直播间的贡献榜上对该用户做了积分 +1,这是一个很典型的「履约」类场景,与我们在直播间内下单购买,履约给仓储系统道理是一样的,但是这都建立在这个流程模式是固化的。

而活动往往不是这样的,活动相比这些固化的流程是更为灵活的。

笔者的团队曾经开发过这样一个场景:

用户通过送礼,去帮助主播完成一个直播间虚拟能量条的冲击,而每充满这个能量条都希望做一件事情,这个事情就彰显出业务侧的脑洞大开,第一次的活动是希望给某个榜单加上分数,第二次的活动希望是给主播掉落一个虚拟的宝箱,第三次的活动是给用户发送一些抽奖券,依次类推,只要是做过的功能,他都希望这个事情可以去用,时间久了,就会面临频繁去修改这个模块的代码,当它「结束」后,if 条件式的去触发各个代码,或者策略模式的去魔改代码,对于长期建设来看这种方式并不友好,就我看来这只是这个模块结束后要做这么多事情,那么下一个模块如果从这么多事情挑两件岂不是还要再写一堆代码?

因此我们想到了一个相对比较原始的解决方案:总线式服务。

4、信鸽服务的组合能力

与其说组合,更倾向于用「履约」这个词形容会比较恰当一点,因为对于活动来说,信鸽只做「履约」这一侧的功能比较重要,他要解决的是异步场景的分发类问题,而不是对一些系统做一些 adapter 组合集成的能力。

这里以一个活动场景来做示例:

1

音乐人活动的一个简单的页面主要包含了几个模块功能:

  • 1)用户通过送礼、观看等行为完成相关任务;
  • 2)为喜爱的音乐人加进度条积分;
  • 3)当进度条积分完成某个档位后,触发宝箱的掉落、飘屏送礼等。每个档位的类型是不同的;
  • 4)幸运锦鲤的模块。

对于这样一个活动,从开发的角度来看,其实它是由几个模块进行组装的,除了 4 是需要独立开发的,1、2、3 都可以通过现有的系统进行组装,这里 1 抽象为任务系统,2 抽象为进度条系统,3 也可以抽象为宝箱系统、送礼系统等。

下图是对场景抽象化模块的概念:

2

那么难点来了,通过什么手段可以来将这些模块进行组装呢?

通过图中可以看到,当一个模块完成了他的生命周期,可以发送一份数据,路由系统收到了这个数据之后,会帮我们做一层路由的转发,决定数据会路由到下一个系统上去。下一个系统可能是「奖励系统」也可以是「进度条系统」。

同时可以对这个行为进一步细化的抽象,我们希望这个路由系统充当一个「总线」的角色,「信件」代表了每一个系统希望收到的数据,同时对每一个系统都抽象化成一个目的地,如果我们配置了一份路由关系(抽象为信鸽配置),那么这只「信鸽」可以作为将数据信件为我们带到我们想去的任何目的地,那么对于系统的好处是,系统只需要提供自己接入这个路由系统的能力即可,下一次随便是什么活动,可以直接做一些组合关系。

有了信鸽路由这种思路,针对音乐人活动这种场景,我们可以将一个流程的完整链路梳理出来:

3

图中可以看到,我们对任务、进度条等基础模块,做了一些扩展点(EXT-POINT)来满足业务流程的编排,这在中台里是比较常规的解决问题的手段之一,而在系统模块组组合的场景中,多了一层信鸽服务的概念,在【2】、【3】、【4】的处理流程中,都可以由信鸽来决定数据流向哪个系统,事实证明这个方案是可行也并且有效的。

5、整体架构设计

那么如何设计这样一个组合能力的架构呢?

研发的重心可以分为四个层面:

  • 1)履约能力:当代码结束后做到末端的触发。
  • 2)SDK 接入能力:Interface 级别的包装,天然的 Autoconfigure 能力。
  • 3)全局的标识字典:一份数据如何让所有接入的系统都可以达成共识。
  • 4)系统的自动注册能力:接入 SDK 后,自动上报到信鸽服务的统一管理,自动激活,不需要人工的介入开启。

基于这四个层面,架构设计如下图:

4

从图中可以看到:左侧虚线框代表了一个系统的触发,右边虚线框代表了最终另外一个系统的触达。信鸽系统充当了代理的服务,触发的系统只需要接入信鸽 SDK,当执行完自己的职责之后,对数据做一层组装发送给信鸽系统,信鸽系统会根据发送的信鸽 id,寻找到信鸽的相关配置,信鸽 id 决定了数据会流转到那个接入的子系统上,这个流转可以是同步的也可以是异步的(当然大部分都是异步场景),异步主要依赖「RocketMQ」的投递能力,当转发失败后,会对投递的数据结果集进行备份存储,用于定时步长的重试操作。在以往的实践过程中,大部分场景都是异步的链路,不需要获得下一个子系统提供的返回的结果集,并且「RocketMQ」本身投递消息的出错率也是小概率事件(毕竟4台 broker,出错3台的可能性是极低的),相比 RPC 这类通信级别的接口有绝对的优势。

6、提供的SDK能力

6.1 路由触发

在上图中,我们可以看到对于子系统分成了大概 2 类场景,一种是活动业务域,另外一种是非活动业务域,这本身与业务的场景有关,我们希望所有的子系统都可以按照标准去接入 SDK,但是并不能保证每个子系统提供能力都依靠 SDK,对于一些非活动业务域使用了定制化开发的模式来进行桥接工作,这种桥接工作更像是传统的 adapter 和 ESB 总线思路。

而活动业务域的子系统都可以采取接入 SDK 的模式,这里主要会介绍一下异步的设计思路,当一个子系统接入 SDK 后,会在 Spring 容器创建 bean 的时候,默认创建一个 PushConsumer 的 bean ,添加监听信鸽 「fly」 的 「Listener」 ,这样能做到自动消费到路由的消息,对消息进行解析,假设这个系统可以承担的模块能力分别是 S1 / S2 ..., SN 等功能,那么整体实现图如下图所示。

5

6.2 自动注册

每一个 Provider 的 Server 都是一个独立的应用服务集群,对于每一个 Provider 来说他提供的能力并不是单一的,正如上文所说,一个领域服务(活动 TOY 服务:主要提供各类积木式玩法领域),他能够提供的模块是非常多样的,诸如宝箱、进度条、留言板等等,「act-toy」 服务他具备了许多功能,当 「act-toy」 服务接入 SDK 后,就需要把自己子域需要发布成 Provider 的功能注册到信鸽上,注册的方案如下图。

当一个 Server 拉取 SDK 的启动后,会定时的拉取定义好的 Interface ,对实现的 Class ,获取自定义注解的 Type 类型,通过顺序消息的方式注册到信鸽服务上,采用了启动 + 定时推送的方式,信鸽服务收到相关的注册信息可以后会将其存储。

6

7、与ESB消息总线方案的比较

从整个组合的能力上看,整体的设计思路是符合总线式服务,而总线式服务业内比较经典的,就是 ESB 总线。

ESB 总线这个技术用今天互联网的角度来看是比较过时的,因为他本身更适应用于大型 IT 企业内部的一些跨语言类服务架构方案,而他本身承接了系统和系统之间的桥接能力。但是信鸽本身的设计不同于 ESB 总线,他更注重的特点是「哑管道」的概念,忽略集成的适配转换,也不适合做中心化同步的集成。

那么具体的比较如下:

7

那么可以从2个 ESB 场景来介绍信鸽的优势所在,笔者理解的 ESB 总线技术大致分两类:

1)开源的 ESB 解决方案:这类是提供开源的一种框架技术,部署在同一个 Tomcat 容器中,开发若干个 Bundle 可以运行在容器中,并且能做到热替换的作用,但是每个 bundle 需要通信的模式需要定制私有协议。由于这类开源框架较早,提供的通信协议大部分是 WebService 类的,因此在开发过程中,开发的成本会非常高,例如 Apache 早期开源的 ServiceMix 。关于协议,需要写大量的 「wsdl」 做通信,大多数也是依赖 WebService 发布的接口。

2)自研的 ESB 集成能力:笔者曾经参加过一个企业级的项目,大致是 ESB 的总线集成了各路系统,包括了 Rest 协议接口、C++ 服务发布的 WebService 协议、海外第三方公司发布的 WebService 协议,为了打通公司内部 C++ 服务、Java Web 服务与海外第三方系统,采用的就是做一个集成的总线,这里需要太多的协议转换,包括如何解析 「wsdl」 文件读取节点数据,转换成 Json 等等,这类场景在企业级服务是常见的事情,本身不适用互联网场景,侧重点更偏向于同步接口协议的转换,同时还不知道编排的能力,大部分依靠硬编码来解决。

无论 1 还是 2 与我们信鸽设计思路都是不同的,信鸽本质上强调的是异步去做末端的事件,选用轻量的协议结构,在技术上属于同语言系,因此关于字典的定义也是标准化的,也就是说,userId、anchorId 分别代表了用户id和主播id,并不需要任何一方突发奇想定义 uid、aid 这类的字样,由信鸽来定义统一化的字典,同时编排统一收拢到信鸽服务来确定,是可视化的,不需要编写复杂的 XML 节点文件。

8、与Pipeline流水线方案的比较

从总线这个概念来看,或多或少同「管道」是类似的,Pipeline 的思想被广泛应用在诸多技术领域中。

比如:

  • 1)CI/CD 持续集成的场景;
  • 2)开源框架中的流水线设计模式,例如 Netty框架中的网络字节流处理等;
  • 3)业务自定义的一些工作流流转技术。

第一种场景:更倾向于 DevOps 的解决方案,从持续集成,持续交付,持续部署,为了快速、自动化、可重复的方式的去处理工程,与我们今天要解决的上层业务编排场景是完全不同的两个领域。

第二种场景:本质上是一种代码实现的设计模式,像 Netty 中,采用的是「责任链」的设计模式去实现,网络字节流经过「工厂流水线」后,进行包装,最后得到一个成品,与我们今天要解决的业务同样不是一个领域。

第三种场景:是业务开发过程中经常遇到的问题,尤其是有复杂流程的场景中,这里包含了对流程的编排、服务的编排,每个代码块和服务都可能作为一种处理的「节点」,在整个流水线中进行串排完成业务的实现。与我们的信鸽有什么不同呢?

8

在我看来,两种技术方案是可以同时存在的,我们对已经稳定的领域场景,做一些灵活自定义的一些流程编排,这些流程可以作为「流水线」的思路去实现,在「流水线」末尾的一个流程节点上可以定位为「信鸽」的节点,这个节点可以再继续自由组合定制化的活动场景。

9、信鸽服务的消息转发能力

9.1 面临的问题

直播的活动与营销活动的不同,大部分触发的场景偏向于平台的下游,因此需要监听许多 topic,去实现自己的业务编码,而活动在非成熟的形式背景下,必然要面临大量的短期代码,短期代码的生命周期往往只局限于活动周期内,这类代码代表了探索,代表了拓荒。直播活动在领域建模上是具备双面性的,一方面要在历史的经验去做经验复制和沉淀,另一方面要具备快速展开短期代码的能力。而随着服务数量的增多,就面临着许多服务要监听 topic,而这些 topic 对于 A 服务可能是监听过的,对于 B 服务可能也是监听过的,面临着这类问题,我们需要把 topic 接入的代码做一次代码的 Copy 或者做一些包来解决,但是这并不是一个友好的解决方案。同时,这也面临着另外一个问题,另外一个问题就是当一个代码块完成他的使命后,再也没有开启的那天,他接入的 topic 会一直进行空转的消费,我们不可能经常的对 「Nydus」(云音乐版 RocketMQ )管控平台去做一些消费下线,时间久了,消息治理就变得比较棘手起来,就如同我们去分层解决服务循环依赖,没成想有一天异步链路中也出现了一团乱麻。下面这个图代表了 「dev」 过程中遇到的问题:

9

9.2 解决思路

我们希望信鸽可以继续发挥他的优势,它不仅仅是一个只做业务末端履约的一个总线服务,他更应该帮助我们活动域内的服务做好消息治理这方面的工作,首先需要有一个研发思路的转变,就是开发同学需要为自己 「dev」 的模块去提供他的 topic,这个 topic 与自己 「dev」 的模块是一个技术闭环的,如果每一个 「dev」 的模块都具备这样的能力,信鸽只需要发挥他的优势,对本来需要监听的 topic 变成由信鸽转发给模块的 topic。

10

按照这个解决思路的前提下,我们希望信鸽需要具备的就是消息转发的能力,而这个转发的场景可以抽象为三种。

具体是:

1)业务自定义的分发场景:这个场景是一定要做一些特定业务处理的,通过原始消息,做一些业务清洗工作,再去转发到一些业务场景中,属于定制化的场景。

2)注册上报自动分发的场景:这个场景只要保证信鸽服务的 receiver 监听的 topic 足够,那么信鸽就可以自动分发到各个业务模块的 topic 上,其中分发的消息结构是具备异样的,可以通过 tag 的不同让 consumer 自住选择性拉取,根据 tag 的区分,同时也能解决某类 topic 消息过多,导致饿死的场景,犹如上图提到的一样。

3)消息存储的场景:并不是所有的消息都需要存储,在活动中我们认为较为重要的消息是需要做备份,方便后期去做回放,例如礼物消息,是任何场景使用率较高的,为了提升写入的能力,可以采用批量消费的消费方式,做batch的写入,在写入这里初步选型是用TiDB,TiDB相对DDB(网易分布式数据库)比较适合去存这类消息,同时,这类消息大部分时候是不需要做读操作。

按照提到的三个场景,整体的架构如下:

11

我们的 receiver 可以根据配置过的 topic 动态的在 spring 容器里创建 consumer 的 bean,兼容了新接入 topic,需要修改信鸽服务代码的问题,同时在收到了 topic 消息的那一刻,消息转发如图中所描述一样,分成了三条路。

这三条路代表了:自动转发、自定义转发、消息存储,这里选用三个不同的 consumer,保证消费线程池的足够宽,和转发 topic 队列足够可消费,自动转发收到消息后会根据源 topic 的类型再一次做 Tag 区分的转发到配置的 topic 关系上,同时这个转发关系是可以让研发自助管理的,也可以配置他的存活周期,非常适用于活动短期场景,在活动结束后,减少了服务的空转消费的情况。当然毕竟也要考虑到另外一个问题,多了一跳的操作会导致到转发的失败,对于这种场景,我们会对失败的消息做 exception 的存储,进行重试处理。

同时:我们对信鸽的转发能力做了压力测试,队列的长度设置的足够宽,不考虑写表链路的场景下,单纯的转发能力,消耗 io 的点主要还是集中在 broker 通信的场景上,如果考虑消息都采用异步落盘的情况下,系统的吞吐量会更优,选用了 「8U16G」 的服务器配置,在 32 台 docker 云容器的支撑下,receiver 是可以承担到 300w/min 的消息量,并且 cpu 还能保持到 45% 左右。

虽然信鸽的转发能力解决了我们的问题,但并不代表这是个最优解,我希望的最优解是可以让信鸽搭载 FaaS 平台,毕竟 FaaS 可以提供很多关于消息清洗的场景,而且 FaaS 在机器资源调度上会有更好表现。FaaS + BaaS 这样的组合,是未来系统技术转型的趋势。

10、本文小结

花费时间做一个系统到底带来什么样的好处,又遇到什么难点呢?

10.1 开发维护性

这里用一个 case 来描述研发在使用信鸽服务做业务开发后会带来怎样的好处,大勇作为一名业务研发同学,今天他需要开发一个活动,活动涉及到榜单晋级、直播间杀怪、直播间飘屏、主播任务。

这个活动的流程思路是:

  • 1)主播完成了 「xx」 任务后在直播间掉落一个「怪物」,并发送飘屏通知。
  • 2)主播完成了 「xx」 任务后给榜单加上一些分数。
  • 3)榜单跨日晋级 topN,topN 发送飘屏通知。

很幸运的是、以上涉及到的功能,以前都开发过了,这一次只需要完成组合,很不幸的是,大勇需要开发这类组合能力。直到他遇到了信鸽,一切迎刃而解了,每个功能都接入了信鸽,那么大勇只需要通过信鸽后台,配置好任务完成要飞向「杀怪」、「飘屏」等等信鸽,对于大勇可能只需要很短的时间提测了,发布流程也减少了,一次性的胶水组装代码也不需要,毕竟完成任务掉落怪物,这个逻辑写在任务系统好像也不是很合适。

10.2 遇到的困难

在做消息管理这类问题处理的时候,实现业务覆盖的时候遇到了很多难点。

我们要对现有系统已经接入的 topic 做一些改造,在新业务场景中是屡试不爽的,但是在旧系统中(例如任务系统),新提供了一个任务的 topic,收发路由的旧消息,虽然我们也按照 SDK 的方式做了根据 tag(源 topic)做一些不同 service 的区分,但是这里依然避免不了关于数据清洗和数据结构的协议转换问题,这类问题可能与任务系统本身的清洗思路是有问题的,而这类问题最佳的解决方案一般可以选用脚本语言去做消息清洗会更加灵活一点。

同时在做容量评估的时候,初期的压测并不是很顺利,由于写表链路与信鸽服务存在一个服务中进行压测,如果对某些消息进行数据写入 TiDB 的话,即便是批量消费写入整体服务的吞吐量也是难以压上去的,因此对写TiDB链路的服务单独进行独立,单独进行这方面的压测,信鸽原始服务只做转发,而写存储这一块可以单独去做写能力的评估。

去掉了写 TiDB 链路的场景,单独对服务的进行吞吐量压测,起初选用的 「4U8G」 的服务器资源,由于整体的转发性能较为吃 cpu ,当提升了规格之后,整体的吞吐量有了很明显翻倍,而介于线上消息量的评估,100w/min 的消息可能是我们现有业务的极限状况,我们分别按照不同消息量做了压测,最后输出了一个压测结果集,会根据不同的消息量区间做适当的扩容和缩减。

10.3 未来展望

本文就云音乐大直播活动中台技术团队在日常研发过程中遇到关于业务场景组合、消息管理这类问题,提供了一种系统设计的思路,希望可以帮助读者在日常开发提供一些参考的意见。

目前主要还是围绕着解决现有技术侧问题所展开的,后期会考虑对消息回放这一块作为修复数据的一个重要手段和解决方案,站在异常处理的视角上,如何帮助研发同学快速修复线上问题。

同时,面对未来国际化的场景下,对于消息用户地区机房不够敏感的场景下,希望可以通过一些手段来帮助业务侧消息转发到相关机房,协助 「Nydus」国际化后,解决业务侧路由的不清晰之处,而在未来的国际化路由机房的基础上,如果做到模块之间消息可以准确的路由到用户所在机房也是我们需要更加深入思考的问题。

我们希望信鸽可以作为直播相关产品在活动业务域的重要解决手段之一,帮助更多相关同学解决「复用」、「组合」的烦恼,同时希望它可以国际化,适应更多的产品场景之中。

11、参考资料

[0] 移动端实时音视频直播技术详解(一):开篇

[1] 海量实时消息的视频直播系统架构演进之路(视频+PPT)

[2] 百万在线的美拍直播弹幕系统的实时推送技术实践之路

[3] 阿里电商IM消息平台,在群聊、直播场景下的技术实践

[4] 微信直播聊天室单房间1500万在线的消息架构演进之路

[5] 百度直播的海量用户实时消息系统架构演进实践

[6] 百万人在线的直播间实时聊天消息分发技术实践

[7] 直播间海量聊天消息的架构设计难点实践

[8] vivo直播系统中IM消息模块的架构实践

[9] 万人群聊消息投递方案的思考和实践

[10] 海量实时消息的视频直播系统架构演进之路(视频+PPT)[附件下载]

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

[12] 阿里IM技术分享(八):深度解密钉钉即时消息服务DTIM的技术设计

[13] B站IM消息系统的新架构升级实践

[14] 视频直播技术干货(九):千万级直播系统后端架构设计的方方面面

[15] 视频直播技术干货(十):一文读懂主流视频直播系统的推拉流架构、传输协议等

[16] 视频直播技术干货(十三):B站实时视频直播技术实践和音视频知识入门

即时通讯技术学习:

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

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

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

posted @ 2026-06-08 11:52 Jack Jiang 阅读(62) | 评论 (0)编辑 收藏

Jack Jiang的 Mail: jb2011@163.com, 联系QQ: 413980957, 微信: hellojackjiang