Jack Jiang

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

2025年11月25日

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

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 阅读(28) | 评论 (0)编辑 收藏

一、基本介绍

1

MobileIMSDK-Web是一套纯JS编写的Web端IM即时通讯框架(含服务端):

  • 1)超轻量级、极少依赖;
  • 2)纯JS编写、高度提炼,简单易用;
  • 3)基于著名的socket.io网络库实现,浏览器兼容性好、服务端并发性能好;
  • 4)支持运行于iOS、Android等移动端浏览器和各种PC端浏览器;
  • 5)能与MobileIMSDK的APP版(原生移动端代码编写)完美互通;
  • 6)可应用于手机端/PC端的网页聊天应用、企业OA、Web端消息推送等场景。

☞ 补充说明:MobileIMSDK-Web是 MobileIMSDK 的姊妹工程,MobileIMSDK-Web专注于Web端网页聊天(或推送),而MobileIMSDK用于原生代码编写的移动端IM(或推送)应用,但二者可完美互通——从而实现原生代码编写的移动端与基于html的网页聊天完美互通。

☞ 关于为何使用的是Socket.io而不是Netty作为MobileIMSDK-Web的网络层,详见:MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?》。

二、与MobileIMSDK的区别

2

☞ 关于MobileIMSDK

MobileIMSDK主要使用原生代码编写,应用于非Web网页方式的移动端即时通讯场景下(当然最新的MobileIMSDK框架也支持基于HTML5的WebSocket客户端)。

⭐️ 同步开源地址:

☞ 关于MobileIMSDK-Web:
MobileIMSDK-Web完全使用JavaScript编写,主要应用于不支持HTML5的需要兼容旧式浏览器的Web网页方式的即时通讯场景下(包括但不限于手机端、PC端的网页聊天(或消息推送)等)。

☞ MobileIMSDK与MobileIMSDK-Web的互通:

基于MobileIMSDK-Web开发的开发的网页聊天等和基于MobileIMSDK开发的移动端IM等可以无缝地进行消息互通,两个框架之间的通信协议完全兼容,从而实现您的网页聊天(或推送)与手机端原生代码开发的IM(或推送)进行完美协作,实现多端通信。

☞ 我该如何选择?

选择一:如果您的应用是用原生代码编写,如移动端是原生代码编写或者不需要兼容旧式浏览器,那么您可以将 MobileIMSDK 引入到您的项目中从而实现IM(或推送)应用;

选择二:如果您的应用是基于Web网页且需要兼容旧式浏览器,那么您的最佳选择就是使用MobileIMSDK-Web来开发您的网页端聊天(或消息推送)。

三、设计目标 

原生的WebSocket代码或者原始的socket.io代码,使得网络通信代码与大量前端UI界面代码混在一起,使得UI界面的重构、维护、改版都非常困难。而MobileIMSDK-Web工程将让开发者专注于UI应用层的开发,网络通信层专业的代码交由SDK开发人员,从而解偶Web端IM的UI前端和通信层的耦合性,同时大大降低复杂性。

总结一下,MobileIMSDK-Web的设计目标是为您的Web端IM带来以下便利:

  • 1)前端UI代码与网络通信代码解耦:UI界面的重构、维护、改版都非常容易和优雅;
  • 2)服务端网络通信代码与业务代码解耦:使得服务端的业务逻辑实现起来清晰简单;
  • 3)浏览器端的高兼容性:受益于socket.io框架,MobileIMSDK-Web在不支持WebSocket的旧式浏览器上仍可很好地工作;
  • 4)服务端的高并发、高性能:得益于Nodejs的异步编程模型和高并发特性,基于MobileIMSDK-Web编写的IM服务端拥有极高的并发处理性能。

四、框架组成 

整套MobileIMSDK-Web框架由以下2部分组成:

  • 1)浏览器端SDK:用于开发浏览器端页面,纯JS编写,极少依赖,方便对接基于原生JS、Angular、EmberJS、VUE等各种前端框架;
  • 2)服务器端SDK:用于开发Web端IM的服务端,支持高性能和高并发。

目录+精编源码

五、技术亮点

  • ✅️ 轻量易使用:超轻量级——纯JS编写且极少依赖,高度提炼——简单易用;
  • ✅️ 兼容性好:基于socket.io网络框架,浏览器兼容性好,在不支持WebSocket的旧式浏览器上仍可很好地工作;
  • ✅️ QoS机制:完善的消息送达保证机制(真正ACK应答机制),确保不漏过每一条消息;
  • ✅️ 断网恢复能力:拥有网络状况自动检测、断网自动治愈的能力;
  • ✅️ 支持多种设备:支持运行于iOS、Android等移动端浏览器和各种PC端浏览器;
  • ✅️ 封装的通信协议:实现了一个对上层透明的即时通讯通信协议模型;
  • ✅️ 身份认证机制:实现了简单合理的身份认证机制(socket.io官方并未实现之,资料也几乎没有);
  • ✅️ 全消息路径:实现了client to server、server to client、client to client 共3种消息路径(socket.io官方只演示了广播消息,一对一发送无资料);
  • ✅️ 服务端慢io解偶:开发者可通过使用MQ进行DB等慢io的读、写解偶,保证IM实时消息高吞吐和性能;
  • ✅️ 服务端代码解偶:实现了上层应用代码与sdk核心代码的解偶,上线、下线、c2s消息、c2c消息、身份认证等的回调通知;
  • ✅️ 实现了在线列表:服务端实现了一个高性能的在线用户列表机制;
  • ✅️ 完善的log记录:服务端接入了log4js日志框架,确保MobileIMSDK-Web中的每一个关键步骤都有日志输出,让您的运行调试更为便利;
  • ✅️ 浏览器端代码解耦:实现了UI前端代码与sdk网络通信代码解偶,防止前端代码跟IM核心代码混在一起,不利于持续升级、重用和维护;
  • ✅️ 轻松开启数据加密:一个参数即可开启SSL/TLS通信加密;
  • ✅️ 聊天协议兼容:实现了与MobileIMSDK 完全兼容的协议模型。

MobileIMSDK-Web的浏览器兼容性:

x1

MobileIMSDK-Web的兼容性由socket.io网络框架决定:点此查看兼容性说明

六、性能负载

得益于socket.io网络框架的高性能和Nodejs的异步编程模型,MobileIMSDK-Web可支持单机数万甚至上十万并发连接。当然,每种应用场景都有各自的特点和差异,请视具体场景具体评估之,性能数据仅供参考。(关于为何使用的是Socket.io而不是Netty作为MobileIMSDK-Web的网络层,详见《MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?》)

☞ socket.io性能测试讨论:socket.io 高并发实战socket.io保持6万+连接测试?如何实现单服务器300万个长连接的?

七、开发者手册

缩略清单-无阴影

八、Demo运行截图

运行效果all_in_one拼合图-opti

九、产品案例

以下为基于MobileIMSDK-Web的Web端IM产品案例 RainbowChat-Web 的产品情况。

rbpw_tb_main1

下图为RainbowChat-Web的主界面更多截图更多演示视频):

main1

下图为RainbowChat-Web的主界面[聊天窗全屏时]更多截图更多演示视频):

main2

本文内容引用自:http://www.52im.net/thread-959-1-1.html

posted @ 2026-06-01 10:51 Jack Jiang 阅读(28) | 评论 (0)编辑 收藏

本文由ArchSynapse AI分享,有修订和重新排版。

1、引言

在为大型语言模型(LLM)应用构建实时前后端通信系统时,选择正确的底层技术至关重要。

本章节将深入剖析三种主流技术的核心原理:

  • 1)Server-Sent Events (SSE):它作为服务器主导的单向数据流的黄金标准;
  • 2)WebSocket:作为通用的全双工通信解决方案;
  • 3)WebRTC:专注于媒体流和点对点数据交换。

理解它们的技术设计哲学和技术细节,是做出明智技术选型的关键基础。

本文将为读者剖析 SSE、WebSocket、WebRTC 的技术原理,并对比三者在性能、安全与架构方面的优劣势,详解了AI大模型(LLM)在实时通信协议方面的综合技术考量以及最终选择。

cover-opti2

2、AI大模型实时通信技术专题

技术专题系列文章目录如下,本文是第 5 篇:

3、SSE是什么?

Server-Sent Events (SSE) 是一种基于标准 HTTP 协议的技术,专为服务器向客户端推送单向事件流而设计。

1

其工作流程始于客户端创建一个 EventSource 对象,该对象会自动发起一个持久的 HTTP GET 请求。服务器接收到请求后,不立即关闭连接,而是保持响应打开,并以特定格式发送数据。

这个格式要求服务器设置 Content-Type 头部为 text/event-stream

每个事件由一系列字段组成,包括:

  • 1)必需的 data: 字段用于承载消息内容;
  • 2)可选的 event: 字段用于定义事件类型;
  • 3)id: 字段用于标识事件以便在连接中断后恢复;
  • 4)以及 retry: 字段用于指定重新连接前的等待时间。

消息之间必须以两个换行符 \n\n 分隔。

这种机制使得服务器能够在一个长连接上持续不断地推送增量数据,非常适合 LLM 流式传输等场景。SSE 的一大优势在于其天然地融入了现有的 HTTP 生态,能够无缝通过反向代理、负载均衡器和防火墙,无需特殊配置。

☞ 进一步学习:

4、WebSocket是什么?

WebSocket 则提供了一种更为强大和灵活的全双工通信协议。它通过一次性的 HTTP 握手来“升级”一个标准的 TCP 连接。

2

握手过程涉及客户端发送一个包含 Upgrade: websocket 和 Connection: Upgrade 等特定头部的 HTTP 请求。如果服务器支持 WebSocket,它会返回一个状态码为 101 (Switching Protocols) 的响应,从而完成协议切换。

一旦连接建立,客户端和服务器就可以随时独立地向对方发送消息,而无需像 HTTP 那样每次都发起新的请求。

WebSocket 消息被封装在轻量级的帧中进行传输,支持文本和二进制数据,极大地降低了网络开销。

这种持久且双向的特性使其成为聊天应用、在线游戏和实时协作工具的理想选择。然而,其复杂性也远超 SSE,因为它是一个独立的协议栈,需要专门的服务器库和客户端处理逻辑。

☞ 进一步学习:

5、WebRTC是什么?

WebRTC (Web Real-Time Communication) 是一个更为复杂的生态系统,旨在实现浏览器间的直接音视频通话和任意数据交换。

3

它的核心思想是尽可能地建立点对点(Peer-to-Peer, P2P)连接,从而绕过中间服务器传输大量媒体数据,以达到最低的延迟。

WebRTC 的整个通信过程分为三个阶段:

  • 1)首先是通过信令服务器交换初始信息;
  • 2)其次是通过 ICE (Interactive Connectivity Establishment) 框架进行 NAT 和防火墙穿越;
  • 3)最后才是建立 P2P 数据通道。

ICE 框架依赖于 STUN (Session Traversal Utilities for NAT) 服务器来帮助设备发现自己的公网 IP 地址,如果直接连接失败,则会降级到 TURN (Traversal Using Relays around NAT) 服务器进行中继。

TURN 服务器虽然能保证连接成功,但会消耗大量带宽,因为所有流量都需要经过它转发。

WebRTC 的另一个关键组件是 RTCDataChannel API,它允许在两个浏览器之间建立一个类似 WebSocket 的 P2P 数据通道,用于传输非媒体数据。

由于其架构的复杂性,WebRTC 通常需要一个额外的信令层(常由 WebSocket 或 SSE 实现)来协调连接的建立。

4

 

☞ 进一步学习:

  1. 访谈WebRTC标准之父:WebRTC的过去、现在和未来
  2. WebRTC实时音视频技术的整体架构介绍
  3. 新手入门:到底什么是WebRTC服务器,以及它是如何联接通话的?
  4. WebRTC实时音视频技术基础:基本架构和协议栈
  5. 实时音视频入门学习:开源工程WebRTC的技术原理和使用浅析
  6. 零基础快速入门WebRTC:基本概念、关键技术、与WebSocket的区别等

6、兼容性、性能与开发复杂度对比

在为 LLM 应用选择实时通信技术时,必须在兼容性、性能和开发复杂度这三个关键维度上进行权衡。本章节将对 Server-Sent Events (SSE)、WebSocket 和 WebRTC 进行全面的横向对比,以揭示它们在不同方面的优劣,从而为技术选型提供数据驱动的依据。

6.1 兼容性与平台支持

首先,在兼容性与平台支持方面,SSE 和 WebSocket 拥有非常广泛的浏览器支持,几乎覆盖了所有现代浏览器,包括 Chrome、Firefox、Safari 和 Edge。Opera Mini 是少数不支持 WebSocket 和 SSE 的例外。IE 浏览器则完全不支持这两种技术。

相比之下,WebRTC 的支持范围稍窄一些,IE 不支持,旧版的 Edge 也不支持,但同样广泛应用于其他现代浏览器及其移动端版本。

对于 LLM 应用而言,这意味着 SSE 和 WebSocket 能够触及更广泛的用户群体。然而,值得注意的是,随着 HTTPS 成为网页标配,HTTP/2 得到了普及,这彻底解决了过去 SSE 在 HTTP/1.1 下因浏览器并发连接数限制(通常是6个)而导致的性能瓶颈,使得 SSE 在现代 Web 架构中表现得更加稳健和高效。

6.2 延迟性能

其次,延迟性能是衡量实时通信技术优劣的核心指标。

理论上,WebSocket 具有最低的延迟,因为它在握手完成后就建立了一个纯粹的、无头开销的数据通道。WebRTC 在 P2P 模式下可以实现极低的延迟,因为它避免了服务器中转。

然而,WebRTC 的延迟受到 NAT/防火墙穿越过程的影响,可能需要 fallback 到较慢的 TURN 中继服务器。

SSE 的延迟取决于底层的 HTTP 版本。在 HTTP/1.1 下,由于队头阻塞(Head-of-Line Blocking),多个 SSE 连接可能会相互阻塞。但在 HTTP/2 下,得益于其强大的多路复用能力,SSE 的性能得到了极大提升,可以与常规 HTTP 请求并行,延迟接近 WebSocket。

一项针对真实世界数据流的测试表明,SSE 和 WebSocket 在 EPS(每秒事件数)方面表现出相当的性能,WebSocket 在高并发下略占优势,但差异在实践中往往可以忽略不计。

因此,对于大多数 LLM 文本流式传输场景,SSE 在 HTTP/2 下已经提供了足够低的延迟。

6.3 开发复杂度

最后,开发复杂度是决定项目成本和上线速度的关键因素。

在这三者中,SSE 的开发复杂度最低。它基于标准的 HTTP,开发者可以利用现有的 Web 服务器和工具链,客户端代码也极为简洁,只需几行 JavaScript 即可启动 EventSource 并监听事件。

WebSocket 的复杂度显著更高。除了协议本身,开发者还需要处理连接管理、状态同步、手动实现心跳包和自动重连逻辑、应对粘性会话(Sticky Sessions)带来的负载均衡挑战,以及在分布式系统中使用 Redis Pub/Sub 等消息队列来广播消息。

WebRTC 的开发复杂度最高,它不仅包含了上述 WebSocket 的所有挑战,还引入了信令、ICE 候选者收集、STUN/TURN 服务器的配置和维护等一系列全新的难题。此外,WebRTC 的安全模型也更为复杂,需要同时保护信令通道和媒体通道。

5

综上所述:SSE 在兼容性、性能和开发复杂度之间取得了最佳平衡,尤其是在 HTTP/2 得到普及的今天。WebSocket 提供了更高的灵活性和更低的理论延迟,但代价是巨大的开发和运维复杂性。WebRTC 则因其固有的复杂性,仅适用于特定的 P2P 和媒体流场景。

7、安全性考量与风险缓解策略

在 LLM 应用中采用实时通信技术时,安全性是不可忽视的核心要素。本章节将深入探讨 Server-Sent Events (SSE)、WebSocket 和 WebRTC 在安全性方面的关键考量、潜在风险及相应的缓解策略。

7.1 Server-Sent Events

对于 Server-Sent Events (SSE),其安全性很大程度上继承自标准的 HTTP 安全模型。

首要的安全措施是强制使用 HTTPS,因为 SSE 的长期连接窗口使其更容易受到中间人攻击(Man-in-the-Middle),窃听或篡改流中的数据。

身份验证是另一个关键环节。由于 EventSource API 不支持自定义 HTTP 头部,传统的 JWT 令牌传递变得困难。常见的解决方案是通过 URL 查询参数传递短时效的访问令牌,但这存在安全隐患,因为令牌可能被记录在服务器日志或浏览器历史中。

另一种更安全的方式是利用带有 SameSite=Strict 属性的 Cookie 来进行认证,这种方式能自动随请求发送,但需要注意 CSRF(跨站请求伪造)攻击的风险,可以通过检查 Origin 头部来缓解。

此外,SSE 端点容易受到 DoS(拒绝服务)攻击,攻击者可以通过建立大量连接耗尽服务器资源。对此,应实施严格的速率限制,限制来自同一 IP 或用户的并发连接数。

7.2 WebSocket

WebSocket 的安全性挑战更为复杂。

虽然 WSS (WebSocket Secure) 使用 TLS 加密来保护数据传输,但其状态化的性质带来了新的攻击面。

首先,WebSocket 缺乏标准化的身份验证机制,开发者必须自行实现,例如在握手阶段通过 Cookie 或查询参数传递凭证。如果未正确验证 Origin 头部,应用将面临 Cross-Site WebSocket Hijacking (CSWSH) 攻击,恶意网站可以在未经授权的情况下建立 WebSocket 连接并访问敏感数据。

其次,WebSocket 容易受到 DoS 攻击,特别是通过 Flood 攻击(发送大量小消息或建立海量连接)来耗尽服务器 CPU 或内存。有效的防御措施包括要求连接前进行认证、限制消息大小和频率、以及实施精细的速率控制 [89]。输入数据也可能导致注入攻击,如 SQL 注入或 XSS,因此所有传入的消息都必须经过严格的验证和清理。

最后,由于 WebSocket 连接是持久的,如果令牌在连接期间过期,可能会导致会话劫持,因此需要实现 token 刷新机制。

7.3 WebRTC

WebRTC 的安全性设计更为严格,其核心理念是端到端加密。

所有媒体流都默认使用 SRTP (Secure Real-time Transport Protocol) 加密,而数据通道则使用 DTLS (Datagram Transport Layer Security) 加密,确保只有通信双方可以解密数据。

然而,WebRTC 的安全性并非万无一失,其脆弱性主要集中在基础设施层面。最著名的安全问题是 IP 地址泄露。由于 ICE 协商需要交换候选者的 IP 地址,即使用户使用了 VPN,其真实的本地和公网 IP 仍可能暴露给通信的另一方,这严重损害了隐私。

缓解此问题的方法是在网络拓扑中强制使用 TURN 服务器中继,从而隐藏客户端的真实 IP。信令通道的安全性至关重要,如果信令服务器未使用 WSS (WebSocket Secure) 或 HTTPS,攻击者可以拦截信令消息,从而发起会话劫持或拒绝服务攻击。

此外,TURN 服务器若配置不当,可能被滥用进行 DDoS 攻击或数据窃取。

最后,尽管 WebRTC 协议本身很安全,但其浏览器实现可能存在漏洞,需要及时更新浏览器以修补已知的安全问题。

6

 

8、架构设计与可扩展性挑战

为 LLM 应用选择实时通信技术不仅关乎功能实现,更深刻地影响着系统的整体架构设计和未来的可扩展性。本章节将深入探讨使用 Server-Sent Events (SSE)、WebSocket 和 WebRTC 时面临的架构挑战,特别是在大规模生产环境下的可扩展性问题。

8.1 Server-Sent Events

使用 Server-Sent Events (SSE) 的架构相对简单且更具弹性。

由于 SSE 基于标准的 HTTP,它可以轻松地与现有的无状态微服务架构集成。服务器可以水平扩展,因为每个 SSE 连接都是独立的,负载均衡器可以将新连接分发到任何可用的服务器实例。

然而,当需要向所有订阅用户广播消息时,简单的无状态架构会遇到挑战。解决这个问题的常见模式是引入一个中央消息代理,如 Redis Pub/Sub 或 Kafka。所有服务器实例都订阅同一个 Redis 频道,当有新消息需要广播时,只需将消息发布到该频道,Redis 会将其高效地分发给所有相关的 SSE 客户端。这种发布/订阅(Pub/Sub)模式极大地简化了广播逻辑,并提高了系统的可扩展性。

此外,SSE 的 stateless nature 使其非常适合在云原生环境中运行,例如 Kubernetes 或 serverless 平台,因为它不需要在节点间共享会话状态。

8.2 WebSocket

相比之下,WebSocket 的架构设计则充满了挑战,其根本原因在于连接的有状态性。

每个 WebSocket 连接都会占用服务器的一个文件描述符,并持续消耗内存和 CPU 资源。当用户数量从数百增加到数万甚至数百万时,这种状态化特性会迅速成为瓶颈。

传统的负载均衡策略(如轮询或最少连接)无法有效分配 WebSocket 连接,因为客户端断线重连后很可能需要回到最初的服务器实例以保持会话状态。

为了解决这个问题,通常需要采用“粘性会话”(Sticky Sessions)或“会话亲缘性”(Session Affinity)的负载均衡策略,但这会导致负载分布不均,并形成单点故障。

更高级的解决方案是构建一个全局状态管理系统,例如将所有连接状态存储在 Redis 等外部数据库中。当服务器 A 接收到需要路由到服务器 B 上某个用户的 WebSocket 消息时,它可以从 Redis 获取目标用户的连接信息并进行转发。这种方法虽然可行,但极大地增加了系统的复杂性和延迟。因此,许多团队最终会选择使用专门的实时通信平台(如 Ably 或 Pusher)来托管这部分基础设施,从而将焦点重新放回业务逻辑上。

8.3 WebRTC

WebRTC 的可扩展性问题则源于其点对点(P2P)的本质。

纯 P2P 架构在小规模群组(例如,最多 4-5 人)内工作良好,因为每个参与者只需要与其他成员建立连接即可。然而,当参与者数量增加时,连接数量会呈二次方增长(N² problem),导致每个客户端的带宽和 CPU 负载急剧上升,很快就会超出个人设备的处理能力。

为了实现 WebRTC 的规模化,必须引入中心化的媒体服务器。

目前主要有两种架构:

  • 1)MCU(Multipoint Control Unit,多点控制单元);
  • 2)SFU(Selective Forwarding Unit,选择性转发单元)。

MCU 会接收所有参与者的媒体流,进行解码、混音/拼接,然后重新编码成一个新的流再分发给所有人,这样客户端只需接收一个流,但 MCU 承担了巨大的计算压力。

SFU 则是一种更高效的架构,它只负责将每个参与者发送的媒体流转发给其他所有人,而不进行任何处理,从而将计算负担分散到各个客户端。SFU 是现代大规模 WebRTC 应用(如 Zoom、Google Meet)的首选架构。

然而,无论是 MCUs 还是 SFUs,都需要大量的服务器资源和复杂的网络基础设施来支持,这对于许多团队来说是一笔巨大的投资。

7

总而言之:

  • 1)SSE 在可扩展性方面提供了最大的灵活性和成本效益;
  • 2)WebSocket 的扩展性挑战巨大,通常需要借助第三方服务或投入大量内部工程资源;
  • 3)WebRTC 的扩展性则最为昂贵和复杂,是专门为媒体流设计的,不适合用于普通的文本数据通信。

9、最终决策与综合建议

在对 Server-Sent Events (SSE)、WebSocket 和 WebRTC 进行了全面的原理、性能、安全性和可扩展性分析之后,本章节将整合所有信息,为 LLM 应用的前后端实时通信提供一个清晰的决策框架,并给出具体的选型建议。

选择何种技术,最终取决于您的 LLM 应用的具体需求、团队的技术栈和预算。我们可以将 LLM 应用的实时通信场景划分为几个典型的层级,每个层级对应不同的技术最优解。

9.1 第一层级:流式文本展示

这是最常见的 LLM 应用场景,用户输入一个问题,然后看到答案逐字或逐词地动态呈现出来。这种模式的核心特征是服务器主导的单向数据流。

在这个场景下,Server-Sent Events (SSE) 是无可争议的最佳选择。

其理由非常充分:

1)首先,SSE 的设计初衷就是为此类单向推送而优化的,它比 WebSocket 更加轻量和简单。

2)其次,得益于现代浏览器对 EventSource API 的原生支持,客户端实现极其简单,且内置了可靠的自动重连机制,极大地提升了用户体验和应用的健壮性。

3)再者,SSE 基于标准 HTTP,在部署和运维上非常友好,可以无缝融入现有的 web 服务生态,无需为实时通信单独搭建一套复杂的基础设施。

Shopify 在其 2022 年黑色星期五活动中的 BFCM Live Map 成功案例,就是一个有力的证明,他们通过 SSE 将毫秒级的数据更新推送给数十万用户,展现了其在生产环境下的强大能力和可扩展性。

9.2 第二层级:双向实时互动

当应用的需求超越了单向展示,进入真正的“对话”或“协作”阶段时,就需要选择能够支持客户端和服务器双向通信的技术。在这种情况下,WebSocket 是唯一的选择。

典型的用例包括:

  • 1)语音 I/O:OpenAI 的 Realtime API 就是一个绝佳的例子,它利用 WebSocket 实现了低延迟的语音对话,允许用户在 AI 说话时打断,并即时获得反馈。这种场景需要双向的音频流和命令流,是 WebSocket 的典型应用。
  • 2)多智能体系统:当多个 AI Agent 需要在后台协同工作,并需要向前端实时同步各自的思考过程、工具调用结果或状态变化时,WebSocket 提供了必要的双向通道。
  • 3)实时协作编辑:当 LLM 作为协作者与用户共同编写代码、文档或进行头脑风暴时,WebSocket 可以确保用户的每一次修改都能被实时同步给 LLM,反之亦然。

选择 WebSocket 的代价是显著增加的开发和运维复杂度。您需要投入资源来构建和维护一个能够处理成千上万个并发长连接的系统,并解决由此带来的一系列可扩展性问题。

9.3 第三层级:点对点媒体流与数据交换

这是 WebRTC 的专属领域。虽然 WebRTC 也能传输数据,但它的核心价值在于绕过服务器直接传输高质量的音视频流。

在 LLM 应用中,WebRTC 的应用场景非常具体且有限:

  • 1)高级语音交互:当需要处理原始的、高保真的音频流,而不是经过编码的文本或语音片段时,WebRTC 可能是必要的底层传输协议。
  • 2)P2P 文件共享:如果 LLM 应用涉及到用户之间直接通过浏览器共享由 LLM 生成的大文件,WebRTC 的 DataChannel 可以提供一条绕过服务器的高速通道。

除非您的应用明确属于上述范畴,否则不应轻易选择 WebRTC。它的高昂复杂性和成本使其成为一个非常规的选择。

9.4 综合决策框架

8

 

9.5 最终结论与建议

对于绝大多数 LLM 应用的前后端通信需求,我们强烈建议从 Server-Sent Events (SSE) 开始。

它足够强大,足以满足当前主流的流式文本展示需求,同时又足够简单,能让团队快速迭代产品。不要为了所谓的“先进性”或过度设计而一开始就选择更复杂的 WebSocket。只有当业务发展到确实需要双向、低延迟的实时互动时,才应该评估并投入资源迁移到 WebSocket。

记住,没有一种技术是完美的,最好的技术是那个能够以最低的成本和复杂度解决你当前问题的技术。

10、参考资料

[0] EventSource API Docs
[1] Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
[2] SSE技术详解:一种全新的HTML5服务器推送事件技术
[3] 使用WebSocket和SSE技术实现Web端消息推送
[4] 详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket
[5] 使用WebSocket和SSE技术实现Web端消息推送
[6] 一文读懂前端技术演进:盘点Web前端20年的技术变迁史
[7] WebSocket从入门到精通,半小时就够!
[8] 网页端IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket
[9] 搞懂现代Web端即时通讯技术一文就够:WebSocket、socket.io、SSE
[10] WebRTC实时音视频技术基础:基本架构和协议栈
[11] 零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能
[12] 实时音视频入门学习:开源工程WebRTC的技术原理和使用浅析
[13] 零基础快速入门WebRTC:基本概念、关键技术、与WebSocket的区别等
[14] 大模型时代多模型AI网关的架构设计与实现
[15] 全民AI时代,大模型客户端和服务端的实时通信到底用什么协议?
[16] 通俗易懂:AI大模型基于SSE的实时流式响应技术原理和实践示例
[17] Web端实时通信技术SSE在携程机票业务中的实践应用
[18] ChatGPT如何实现聊天一样的实时交互?快速读懂SSE实时“推”技术

即时通讯技术学习:

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

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

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

posted @ 2026-05-28 11:57 Jack Jiang 阅读(31) | 评论 (0)编辑 收藏

1、写在前面

对于IM系统来说,如何做到IM聊天消息离线差异拉取(差异拉取是为了节省流量)、消息多端同步、消息顺序保证等,是典型的IM技术难点。总结下来其实就是要解决好一个问题:即如何保证聊天消息的唯一性判定和顺序判定。

很多读者在讨论这个问题的时候,普遍考虑的是使用整型自增序列号作为消息ID(即MsgId):这样确实能保证消息的唯一性又方便保证顺序性,但问题是在分布式情况下是很难保证消息id的唯一性且顺序递增的,维护id生成的一致性难度太大了(网络延迟、调试出错等等都可能导致不同的机器取到的消息id存在碰撞的可能)。

1

微信消息序列号实际上是解决消息的唯一性、顺序性问题,可以将一个技术点分解成两个:即将原先每条消息一个自增且唯一的消息ID分拆成两个关键属性——消息ID(msgId)和消息序列号(seqId)。消息ID只要保证唯一性而不需要兼顾顺序性(比如直接用UUID)、消息序列号只要保证顺序性而不需要兼顾唯一性,这样的技术分解就能很好的解决原本一个消息ID既要保证唯一性又要保证顺序性的难题。

那么,如何优雅地解决“消息序列号只要保证顺序性而不需要兼顾唯一性”的问题呢?这就是本文所要分享的内容,强烈建议深入理解和阅读。

2、本文分篇

本文是“IM消息ID技术专题”系列文章的第1篇,总目录如下:

  1. IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》(☜  本文)
  2. IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)
  3. IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略
  4. IM消息ID技术专题(四):深度解密美团的分布式ID生成算法
  5. IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现
  6. IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)
  7. IM消息ID技术专题(七):深度解密vivo的自研分布式ID服务(鲁班)

3、技术背景

微信在立项之初,就已确立了利用数据版本号(注:具体的实现也就是本文要分享的消息序列号)实现终端与后台的数据增量同步机制,确保发消息时消息可靠送达对方手机,避免了大量潜在的家庭纠纷。时至今日,这套同步机制仍然在消息收发、朋友圈通知、好友数据更新等需要数据同步的地方发挥着核心的作用。

而在这同步机制的背后,需要一个高可用、高可靠的消息序列号生成器来产生同步数据用的版本号(注:因为序列号天生的递增特性,完全可以当版本号来使用,但又不仅限于版本号的用途)。这个消息序列号生成器我们微信内部称之为 seqsvr ,目前已经发展为一个每天万亿级调用的重量级系统,其中每次申请序列号平时调用耗时1ms,99.9%的调用耗时小于3ms,服务部署于数百台4核 CPU 服务器上。

本篇将重点介绍微信的消息序列号生成器 seqsvr 的算法原理、架构核心思想,以及 seqsvr 随着业务量快速上涨所做的架构演变(下篇《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》会着重讨论分布式容灾方案)。

2

4、关于作者

曾钦松:微信高级工程师,负责过微信基础架构、微信翻译引擎、微信围棋PhoenixGo,致力于高可用高性能后台系统的设计与研发。2011年毕业于西安电子科技大学,早先曾在腾讯搜搜从事检索架构、分布式数据库方面的工作。

5、技术思路

微信服务器端为每一份需要与客户端同步的数据(例如聊天消息)都会赋予一个唯一的、递增的序列号(后文称为 sequence ),作为这份数据的版本号(这是利用了序列号递增的特性)。在客户端与服务器端同步的时候,客户端会带上已经同步下去数据的最大版本号,后台会根据客户端最大版本号与服务器端的最大版本号,计算出需要同步的增量数据,返回给客户端。这样不仅保证了客户端与服务器端的数据同步的可靠性,同时也大幅减少了同步时的冗余数据(就像这篇文章中讨论的一样:《如何保证IM实时消息的“时序性”与“一致性”?》)。

这里不用乐观锁机制来生成版本号,而是使用了一个独立的 seqsvr 来处理序列号操作:

  • 1)一方面因为业务有大量的 sequence 查询需求——查询已经分配出去的最后一个 sequence ,而基于 seqsvr 的查询操作可以做到非常轻量级,避免对存储层的大量 IO 查询操作;
  • 2)另一方面微信用户的不同种类的数据存在不同的 Key-Value 系统中,使用统一的序列号有助于避免重复开发,同时业务逻辑可以很方便地判断一个用户的各类数据是否有更新。

从 seqsvr 申请的、用作数据版本号的 sequence ,具有两种基本的性质:

  • 1)递增的64位整型变量;
  • 2)每个用户都有自己独立的64位 sequence 空间。

举个例子,小明当前申请的 sequence 为100,那么他下一次申请的 sequence ,可能为101,也可能是110,总之一定大于之前申请的100。而小红呢,她的 sequence 与小明的 sequence 是独立开的,假如她当前申请到的 sequence 为50,然后期间不管小明申请多少次 sequence 怎么折腾,都不会影响到她下一次申请到的值(很可能是51)。

这里用了每个用户独立的64位 sequence 的体系,而不是用一个全局的64位(或更高位) sequence ,很大原因是全局唯一的 sequence 会有非常严重的申请互斥问题,不容易去实现一个高性能高可靠的架构。对微信业务来说,每个用户独立的64位 sequence 空间已经满足业务要求。

目前 sequence 用在终端与后台的数据同步外,同时也广泛用于微信后台逻辑层的基础数据一致性cache中,大幅减少逻辑层对存储层的访问。虽然一个用于终端——后台数据同步,一个用于后台cache的一致性保证,场景大不相同。

但我们仔细分析就会发现,两个场景都是利用 sequence 可靠递增的性质来实现数据的一致性保证,这就要求我们的 seqsvr 保证分配出去的 sequence 是稳定递增的,一旦出现回退必然导致各种数据错乱、消息消失;另外,这两个场景都非常普遍,我们在使用微信的时候会不知不觉地对应到这两个场景:小明给小红发消息、小红拉黑小明、小明发一条失恋状态的朋友圈,一次简单的分手背后可能申请了无数次 sequence。

微信目前拥有数亿的活跃用户,每时每刻都会有海量 sequence 申请,这对 seqsvr 的设计也是个极大的挑战。那么,既要 sequence 可靠递增,又要能顶住海量的访问,要如何设计 seqsvr 的架构?我们先从 seqsvr 的架构原型说起。

6、具体的技术架构原型

不考虑 seqsvr 的具体架构的话,它应该是一个巨大的64位数组,而我们每一个微信用户,都在这个大数组里独占一格8 bytes 的空间,这个格子就放着用户已经分配出去的最后一个 sequence:cur_seq。每个用户来申请sequence的时候,只需要将用户的cur_seq+=1,保存回数组,并返回给用户。

3

▲ 图1:小明申请了一个sequence,返回101

6.1 预分配中间层

任何一件看起来很简单的事,在海量的访问量下都会变得不简单。前文提到,seqsvr 需要保证分配出去的sequence 递增(数据可靠),还需要满足海量的访问量(每天接近万亿级别的访问)。满足数据可靠的话,我们很容易想到把数据持久化到硬盘,但是按照目前每秒千万级的访问量(~10^7 QPS),基本没有任何硬盘系统能扛住。

后台架构设计很多时候是一门关于权衡的哲学,针对不同的场景去考虑能不能降低某方面的要求,以换取其它方面的提升。仔细考虑我们的需求,我们只要求递增,并没有要求连续,也就是说出现一大段跳跃是允许的(例如分配出的sequence序列:1,2,3,10,100,101)。

于是我们实现了一个简单优雅的策略:

  • 1)内存中储存最近一个分配出去的sequence:cur_seq,以及分配上限:max_seq;
  • 2)分配sequence时,将cur_seq++,同时与分配上限max_seq比较:如果cur_seq > max_seq,将分配上限提升一个步长max_seq += step,并持久化max_seq;
  • 3)重启时,读出持久化的max_seq,赋值给cur_seq。
4

▲ 图2:小明、小红、小白都各自申请了一个sequence,但只有小白的max_seq增加了步长100

这样通过增加一个预分配 sequence 的中间层,在保证 sequence 不回退的前提下,大幅地提升了分配 sequence 的性能。实际应用中每次提升的步长为10000,那么持久化的硬盘IO次数从之前~10^7 QPS降低到~10^3 QPS,处于可接受范围。在正常运作时分配出去的sequence是顺序递增的,只有在机器重启后,第一次分配的 sequence 会产生一个比较大的跳跃,跳跃大小取决于步长大小。

6.2 分号段共享存储

请求带来的硬盘IO问题解决了,可以支持服务平稳运行,但该模型还是存在一个问题:重启时要读取大量的max_seq数据加载到内存中。

我们可以简单计算下,以目前 uid(用户唯一ID)上限2^32个、一个 max_seq 8bytes 的空间,数据大小一共为32GB,从硬盘加载需要不少时间。另一方面,出于数据可靠性的考虑,必然需要一个可靠存储系统来保存max_seq数据,重启时通过网络从该可靠存储系统加载数据。如果max_seq数据过大的话,会导致重启时在数据传输花费大量时间,造成一段时间不可服务。

为了解决这个问题,我们引入号段 Section 的概念,uid 相邻的一段用户属于一个号段,而同个号段内的用户共享一个 max_seq,这样大幅减少了max_seq 数据的大小,同时也降低了IO次数。

5

▲ 图3:小明、小红、小白属于同个Section,他们共用一个max_seq。在每个人都申请一个sequence

的时候,只有小白突破了max_seq上限,需要更新max_seq并持久化

目前 seqsvr 一个 Section 包含10万个 uid,max_seq 数据只有300+KB,为我们实现从可靠存储系统读取max_seq 数据重启打下基础。

6.3 工程实现

工程实现在上面两个策略上做了一些调整,主要是出于数据可靠性及灾难隔离考虑:

1)把存储层和缓存中间层分成两个模块 StoreSvr 及 AllocSvr 。StoreSvr 为存储层,利用了多机 NRW 策略来保证数据持久化后不丢失; AllocSvr 则是缓存中间层,部署于多台机器,每台 AllocSvr 负责若干号段的 sequence 分配,分摊海量的 sequence 申请请求。

2)整个系统又按 uid 范围进行分 Set,每个 Set 都是一个完整的、独立的 StoreSvr+AllocSvr 子系统。分 Set 设计目的是为了做灾难隔离,一个 Set 出现故障只会影响该 Set 内的用户,而不会影响到其它用户。

6

▲ 图4:原型架构图

7、本篇小结

写到这里把 seqsvr 基本原型讲完了,正是如此简单优雅的模型,可靠、稳定地支撑着微信五年来的高速发展。五年里访问量一倍又一倍地上涨,seqsvr 本身也做过大大小小的重构,但 seqsvr 的分层架构一直没有改变过,并且在可预见的未来里也会一直保持不变。

原型跟生产环境的版本存在一定差距,最主要的差距在于容灾上。像微信的 IM 类应用,对系统可用性非常敏感,而 seqsvr 又处于收发消息、朋友圈等功能的关键路径上,对可用性要求非常高,出现长时间不可服务是分分钟写故障报告的节奏。

7

本文的下篇《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》会讲讲 seqsvr 的容灾方案演变。另:《微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗》一文中提到的利用sequence序列号实现消息防丢机制的原理,也可以一并阅读之。

8、相关资料

[1] 零基础IM开发入门(四):什么是IM聊天系统的消息时序一致性?

[2] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

[3] IM消息送达保证机制实现(二):保证离线消息的可靠投递

[4] 如何保证IM实时消息的“时序性”与“一致性”?

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

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

[7] 浅谈移动端IM的多点登陆和消息漫游原理

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

[9] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[10] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[11] 阿里IM技术分享(七):闲鱼IM的在线、离线聊天数据同步机制优化实践

[12] 转转平台IM系统架构设计与实践(一):整体架构设计

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

[14] 微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

即时通讯技术学习:

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

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

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

posted @ 2026-05-19 18:07 Jack Jiang 阅读(27) | 评论 (0)编辑 收藏

本文由37手游黄子键分享,有排版和内容优化等。

1、引言

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

cover_opti

2、长连接对于大部分公司的意义

实时的响应总是让人兴奋的,就如你在微信里看到对方正在输入,如你在王者峡谷里一呼百应,如你们在直播弹幕里不约而同的 666,它们的背后都离不开长连接技术的加持。

每个互联网公司里几乎都有一套长连接系统,它们被应用在消息提醒、即时通讯、推送、直播弹幕、游戏、共享定位、股票行情等等场景。而当公司发展到一定规模,业务场景变得更复杂后,更有可能是多个业务都需要同时使用长连接系统。

3、长连接是什么?

长连接,在百科上的定义:指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。

严格上来说:长连接是一种概念,它指的是在网络发送,接收双方保持一个持续连接的状态,双方都可以发送或接收消息(全双工)。

当然,长连接系统听起来好像高深莫测,但实际不可能说完全脱离于我们实际公司的业务去出发设计,这就引出来我们今天的主题—— 长连接技术在37手游内是如何设计以及实践的?

4、技术痛点

从服务端而言:缺乏在SDK内实时推送通知用户的能力,如防沉迷的弹窗通过http定时轮询来实现,给服务造成很大的压力

从客户端而言:也缺乏低成本告知服务端自身在线的能力,如维持用户在线状态靠客户端定时上报心跳到防沉迷服务实现。

5、搭建背景

业务背景:手游的SDK需要提供内嵌直播弹幕的功能,供玩家在看直播的时候进行沟通发言,业界一般会用到长连接来进行实时的推送,以降低服务的轮训和请求。

从业务上而言:直播弹幕需求由于初始量级很少,不用通过长连接的形式也能实现具体业务逻辑;但出于平台的拓展和能力考虑,长连接的能力是必须具备的。

长连接系统的这次搭建,本质是通过SDK内嵌直播弹幕为切入点,从0到1,为平台提供了完整的一套实时推送触达用户的能力。

从以上角度出发:我们便着手想要设计一个高可用且高性能的长连接系统提供给我们使用。

6、方案选型

1

 

从实际上而言,我们考虑三个方向。

1)云服务:

市面上可购买的服务如环信等,大多数都以即时聊天通讯为主,长连接往往只是附带产品,过于偏向于社交业务,在花钱的同时也很难适应到自身游戏业务。

2)开源框架:

b站的goim框架、NettyChat框架、MobileIMSDK框架等,好处是免费且能快速接入,但业务还是不相适应,且语言栈和技术栈不一定相契合。

2

 

3)自研:

开发成本高,且容易踩坑。基础组件完善,统一框架好进行监控和问题查询。且能充分契合自身业务进行拓展,并将数据源掌控在自己手中。

4)结论:

出于扩展性的考虑,肯定是自研的方式更加合适,但完全自研相应的成本和不确定性会非常高,因此最终的选型方案为借鉴b站的goim框架设计和部分代码,并做了符合自身技术栈和业务架构的改善。

可认为是基于开源框架的自研方案的形式完成了长连接这个系统的落地。

7、goim技术概况

7.1 goim是什么?为什么借鉴它?

goim是b站开源研发的一个支持集群的im及实时推送服务。业务上为直播间的弹幕发送场景。

主要考虑到:

  • 1)技术栈契合:语言栈为Golang ,消息队列为kafka,缓存设计为redis;
  • 2)高性能:有压测报告,性能设计有保障;
  • 3)多协议支持:websocket、tcp。

7.2 goim的模块?

3

如上图所示:

  • 1)comet:用于跟端上保连,在内存中存储订阅信息;
  • 2)logic:用于处理鉴权,在线房间等业务逻辑;
  • 3)business:业务服务;
  • 4)balancer:负载均衡模块;
  • 5)discovery:服务发现模块;
  • 6)job:kafka用于削峰。redis用于缓存房间信息;
  • 7)logic和comet的通讯:采用rpc,优化性能。

从上面的goim的设计而言,我们可以总结出长连接的设计原则。

长连接架构的设计总是大同小异的,通用的有这几点:

  • 1)业务层次分明:分接入层,逻辑层,存储层,服务发现层;通过这几层,实现业务解耦
  • 2)协议通用:传输的数据协议必须在所有服务内进行通讯,并支持扩展
  • 3)上行和下行收敛:上行和下行都会有一个对应的网关来进行收敛消息的收敛
  • 4)消息削峰:通过队列来将发送信息削峰。并使接入层和逻辑层解藕

8、手游长连接系统的设计实践

根据以上的通用设计原则,我们不难得出,需要针对goim的架构如何做适配才能实际满足我们的业务需求,下面我们将会一一介绍。

8.1 架构改动

4

从架构而言,我们改动的点不多:

  • 1)cient获取连接节点:手游内无统一的服务发现模块,但有外层LB,因此通过LB来实现comet节点负载均衡
  • 2)推送消息:同上,消费者获取节点时需要到logic服务中查询,而不是discovery
  • 3)服务通讯协议:由于手游内架构通讯协议的统一,接入层和逻辑层的通讯由RPC转为http

8.2 逻辑改动

1)鉴权机制:客户端第一次连接上comet的时候,会发送鉴权上行。comet会解析客户端传输的数据,若包体协议或对应的签名错误,comet会将会直接主动断连

2)心跳机制:comet会有心跳计时器,若客户端无定时上报心跳,则认为该连接已经超时,直接断开。这种业务的心跳主要是为了防止僵尸连接的存在。

3)回包机制:客户端每发送一次上行操作,comet都会有对应的消息回包给端上。端上可根据回包,来知道自己鉴权,心跳,或者订阅,退订的操作是否成功,从而决定是否进行重试。

4)发布/订阅机制:

我们在观看goim的源码时,发现goim适用于直播,群体推送以房间为维度,带有强业务属性,因此我们针对该部分抽象出发布/订阅机制。将房间抽象为topic,修改进房/出房动作为  订阅/退订。

将所有的通知抽象为业务事件,客户端想要接受到哪个事件过来的消息时,可发送对应的订阅上行。单个连接订阅的事件不做限制,对某个用户或某个事件范围内的用户推送消息时,comet会根据事件去取到推送的用户,只有用户订阅了才会收到消息。

5)内存设计:

  • a. bucket维护消息通道和事件的信息;
  • b. 一个session对应一个用户连接;
  • c. 根据sessionid做一致性哈希来选择落到那个bucket上;
  • d. bucket有两个map,一个是session map,一个是topic map;
  • e. 所有bucket都会开启一个chan做监听,广播的时候,会通知到所有bucket,所有bucket再取出某个事件的所有连接进行下发。
5

8.3 总体交互

6

8.4 总体特点

因此,我们可以总结出来,我们手游长连接系统的总体特点:

  • 1)纯golang实现;
  • 2)多协议支持:websocket和tcp;
  • 3)可拓扑结构:主要模块均无状态,可横向扩展;
  • 4)消息支持单推/群推,消息协议业务可自定义;
  • 5)发布/订阅机制,事件可业务自定义。

9、性能评估

在说性能之前,我们先抛出一个疑问:协程数过多实际占用的是什么?连接数过多是否影响CPU?

从实际上来说:长连接的性能瓶颈一般卡在接入层,因此以接入层为评估维度,通过全链路压测得出来结果。

如下:

  • 1)压测参数:3000连接+ 3000 qps的实时推送;
  • 2)推送内容:{msg:test}  ;
  • 3)推送类型:群推;
  • 4)推送持续时长:5分钟 ;
  • 5)资源使用:1核2G的容器;
  • 6)CPU的使用:达到100%。

目前采用的长连接系统,通过压测结果发现,本身的连接数的维持和实时推送实际影响到是机器两个维度的性能,推送量影响到的是CPU,连接数影响到的是内存。分别提高两个参数对相应的内存,CPU的影响并不大。

10、实际应用情况

直播弹幕:

7

 

悬浮球实时红点:

8

从实际上来说,接入的业务虽小,但基础能力已经具备。

11、本文小结

长连接系统从根本上来说,对应的设计都是大同小异的,我们应该更加关注的是对于自身业务的适配以及实现。

12、参考资料

[1] 以网游服务端的网络接入层设计为例,理解实时通信的技术挑战

[2] 知乎千万级并发的高性能长连接网关技术实践

[3] 手淘亿级移动端接入层网关的技术演进之路

[4] 喜马拉雅自研亿级API网关技术实践

[5] 石墨文档单机50万WebSocket长连接架构实践

[6] 小米小爱单机120万长连接接入层的架构演进

[7] B站基于微服务的API网关从0到1的演进之路

[8] 去哪儿网酒店高性能业务网关技术实践

[9] 百度基于Go的千万级统一长连接服务架构实践

[10] 揭秘腾讯公网TGW网关系统的技术架构演进

[11] 基于Netty的携程高性能网关异步改造实践

[12] 手把手教你写基于TCP的Socket长连接

[13] 正确理解IM长连接、心跳及重连机制,并动手实现

[14] 万字长文:手把手教你实现一套高效的IM长连接自适应心跳保活机制

[15] 用JWT技术解决IM系统Socket长连接的身份认证痛点

[16] TCP/IP详解 - 第11章·UDP:用户数据报协议

[17] TCP/IP详解 - 第17章·TCP:传输控制协议

[18] WebSocket从入门到精通,半小时就够!

[19] 快速理解TCP协议一篇就够

[20] 快速理解TCP和UDP的差异

[21] 一泡尿的时间,快速搞懂TCP和UDP的区别

[22] 到底什么是Socket?一文即懂!

[23] 我们在读写Socket时,究竟在读写什么?

[24] 假如你来设计TCP协议,会怎么做?

[25] 深入操作系统,一文搞懂Socket到底是什么

即时通讯技术学习:

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

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

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

posted @ 2026-05-06 22:27 Jack Jiang 阅读(47) | 评论 (0)编辑 收藏

1、关于RainbowChat

 1

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

2、关于MobileIMSDK开源工程

2

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

3

工程同步开源地址:

2、v12.0 版更新内容

此版更新内容更多历史更新日志):

(1)Android端主要更新内容全面适配Android 16、适配16KB page size、适配全面屏特性等】:

  • 1)[bug] 解决了两个表情占位符重复的问题;
  • 2)[bug] 解决了好友列表删除唯一好友后,一直转圈的问题;
  • 3)[bug] 优化了搜索聊天记录时,当首页“消息”中不存在该陌生人时,搜出的群聊详细中消息发送者昵称会用uid显示的问题;
  • 4)[bug] 解决了不支持分区存储的老手机转发的大文件消息,在新系统上无法下载的问题;
  • 5)[bug] 优化了存在多条置顶消息时,不是按置顶时间而是消息时间排序的问题;
  • 6)[新增] 二维码生成界面下方增加功能按钮;
  • 7)[新增] “用户信息”界面增加了“查看用户资料”按钮;
  • 8)[新增] 优化了世界频道的打开入口等;
  • 9)[新增] 去掉了“商城”模块,增加了“发现”页面;
  • 10)[优化] 将核心层提炼成独立的chatkit模块;
  • 11)[优化] 解决了独立chatkit后,好友信息中删除对方时,无法自动跳转到主页的问题;
  • 12)[优化] 现在不能删除首页列表中的“确认提醒”这个item了;
  • 13)[优化] 升级腾讯Bugly至4.1.9.3,解决上架国内应用市场的隐私合规问题;
  • 14)[优化] 登录和退出登录接口中废弃了osType字段;
  • 15)[优化] 优化了注册界面中关于服务端返回邮箱格式不正确的错误码的处理;
  • 16)[优化] 支持小窗、分屏显示;
  • 17)[优化] 只有好友才能查看对方的注册和登录时间;
  • 18)[优化] 查找好友时不再显示对方的在线状态;
  • 19)[优化提升targetSdkVersion至36,全面兼容Android 16
  • 20)[优化开发工程升级适配AGP 9.1最新版
  • 21)[优化] 升级权限框架以适配最新Android 16系统;
  • 22)[优化] 针对全部界面适配系统强制的Edge to Edge全面屏特性
  • 23)[优化] 解决了Android 16下聊天界面输入法弹出时会挡住消息输入框的问题;
  • 24)[优化] 解决基于PopupWindow实现的弹出界面底部在Edge to Edge全面屏特性下的显示问题;
  • 25)[优化] 加固一处因多线程安全问题导致的可能崩溃风险;
  • 26)[优化] 升级高德地图SDK至最新v11.1等,适配google play强制16KB page size问题
  • 27)[优化] 优化了位置消息搜索界面的搜索组件ui并提升了细节体验;
  • 28)[优化] 解决了进入了主页搜索界面在Android 16下不能自动弹出输入法,及优化了点击背景可收起软键盘;
  • 29)[优化] 删减了APP首次启动时的权限申请内容;
  • 30)[优化] 解决了Android 16下返回按钮事件捕获失败的问题;
  • 31)[优化] 聊天界面下方的功能面板图标美化等;
  • 32)[优化] 聊天文本框自动换行;
  • 33)[优化] 其它更具现代感的UI细节优化和体验等;

(2)服务端主要更新内容安全加固、新增接口等】:

  • 1)[bug] 解决了对接RainbowChat-Web产品时,网页端无法正常登录的问题;
  • 2)[优化] 加固了后端SQL防注入逻辑;
  • 3)[优化] 开启了WebSocket协议支持;
  • 4)[优化] 对离线数据表中的消息指纹字段增加了索引,提升查询性能;
  • 5)[优化] 优化了文件下载服务中存在利用文件名进行越权文件操作的安全隐患;
  • 6)[优化] 提供了一个校验token与uid一致性的安全性实现示例;
  • 7)[优化] 优化了原Android专用的登录接口【接口1009】,使之同时支持验证码、密码登录;
  • 8)[优化] 【接口1008-10-22】新增了“preview_count”字段;
  • 9)[优化] 将IDEA工程中applicationContextRoot改成了rainbowchat_pro/(方便开发环境跟生产环境一致);
  • 10)[优化] 优化了注册接口【接口1008-1-7】,增加了手机号和短信验证码支持;
  • 11)[新增] 数据库新增了注销登录相关字段;
  • 12)[新增] 新增注销登录接口;
  • 13)[新增] 新增获取验证码接口【接口1008-1-27】;
  • 14)[新增] 新增新的登录接口【接口1017】,同时支持ios等客户端的验证码、密码登录;
  • 15)[新增] 新增对接鸿蒙NEXT产品时支持华为Push Kit离线推送;

3、升级后的主要UI运行截图

 更多截图请查看:Android端运行截图iOS端运行截图

4_s100_p75

4、真机运行视频

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

5

5、真机实拍截图

6_pct85

posted @ 2026-04-27 17:22 Jack Jiang 阅读(53) | 评论 (0)编辑 收藏

网易技术团队旭风分享,有排版优化和修订。

1、引言

一款社交产品的诞生,离不开即时通讯(IM)场景。随着团队业务版图在社交领域的布局,诞生了多个社交场景APP,涉及的IM场景,包含私聊、群聊、聊天室等。

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

cover_opti

2、业界的实现方案

目前业界有各种即时通讯服务商提供的配套交互层解决方案,其大多以牺牲灵活性来满足快速集成需要,在定制能力上远不能胜任我们业务需要。

再诸如 MessageKit之类的社区IM框架,其在视觉交互表现上功能完备,能帮助我们快速、灵活搭建IM消息流结构,但业务需要的是一套完整的携带消息交互能力的方案,因此对此类框架,仍需要做不小的改造才能适应我们的业务(另一参考方案:MobileIMSDKGitee源码托管地址))。

3、我们的想法

对于一个IM消息流交互层方案,主要考虑几个方面:

  • 1)规范的消息流结构:提供消息流视图结构规范化的构建方式;
  • 2)标准的消息交互能力:统一消息交互能力,业务方按需使用,快速集成;
  • 3)业务拓展性:针对数据源、消息交互能力提供业务灵活拓展点;
  • 4)业务接入成本:内置通用交互方案,降低业务接入成本。

目前,我们存量业务中的IM场景,底层IM能力主要由云信引擎提供。同时又存在基于业务服务端,通过HTTP去交互的场景。

另外,还需要预留后期切换IM引擎的可能性,因此需要将交互层IM能力抽象出来。

此外,为了适应团队现状,减小业务接入成本,考虑将云信提供的交互能力内置在方案中。

4、整体设计

设计愿景:提供标准化的能力,同时对拓展开放。

我们期望一套通用的IM消息流能力,能够在方案上标准化。这里的标准化,主要包含消息流结构构建的标准化,以及消息交互能力的标准化。同时,方案需要在交互能力上适应不同业务场景,因此采用依赖注入的方式,提供业务定制能力。

按照职能划分,将框架整体分为了两层:

1

 

1)消息流结构层:负责消息流结构的构建,定义消息视图、布局、数据上的规范,提供业务层分别在「消息」、「会话」两个维度的配置能力。

2)消息交互层:提供消息能力、消息流、消息数据方面的交互能力,向下依赖交互接口,内置标准交互能力的同时,也支持业务按需注入交互实现。

5、聊天消息流的显示结构

5.1 消息组件

不同的业务场景,消息流样式表现必然有所差异。

下面列出了我们几个业务中的消息流界面:

2

如何设计一套通用的消息流视图结构,满足不同业务需要?经过对各个业务以及一些主流IM工具的观察,将消息视图结构设计成如下结构,是能够满足我们各个IM场景需要的(见下图)。

3

我将消息结构拆分成了5部分,对应5个消息组件  MessageView ,每个消息组件都支持业务对其「样式」、「显隐」、「布局」进行配置,从而满足不同场景定制需要。

MessageView作为基础消息组件,提供了一些标准能力,例如是否响应菜单动作 canPerformMenuAction 、视图重用回调时机 prepareForReuse 、尺寸策略等。

open class MessageView: MessageAbstractView {

  public var canPerformMenuAction = false

    open func refresh(with message: Message) {}

    open func prepareForReuse() {}

    open class func createSizeStrategy(message: Message, fittingSize: CGSize) -> MessageLayoutSizeStrategy? {

    // ...

    }

}

5.2 尺寸策略

消息组件尺寸作为消息流布局上不可或缺的要素,方案提供了多种尺寸计算策略 MessageLayoutSizeStrategy 。

具体是:

  • 1)自动布局计算策略:业务方对消息组件使用 AutoLayout 布局时使用,内部会依据约束自动计算好组件尺寸;
  • 2)SizeThatFit 策略:依据组件 SizeThatFit 方法返回的尺寸进行布局;
  • 3)自定义策略:提供自定义尺寸计算方式。

public protocol MessageLayoutSizeStrategy {

    func caclulateSize(_ sizeViewType: MessageView.Type,

                       message: Message,

                       fittingSize: CGSize) -> CGSize

}

 

public struct MessageAutoLayoutSizeStrategy: MessageLayoutSizeStrategy {

    public func caclulateSize(_ sizeViewType: MessageView.Type,

                              message: Message,

                              fittingSize: CGSize) -> CGSize {

    // ...省略其他代码

        return sizeView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

    }

 

}

 

public struct MessageSizeThatFitsStrategy: MessageLayoutSizeStrategy {

    public func caclulateSize(_ sizeViewType: MessageView.Type,

                              message: Message,

                              fittingSize: CGSize) -> CGSize  {

        // ...省略其他代码

        return sizeView.sizeThatFits(fittingSize)

    }

}

5.3 布局快照

我们还针对消息组件维度支持了布局快照。通常当一个消息组件尺寸固定,在交互过程中尺寸不会发生的情况下,打开布局快照,以减少布局计算消耗。同时也提供了快照清除的能力。

我们对多个消息流在快速滚动过程中的CPU峰值做了统计,在使用自动布局尺寸策略的情况下,开启布局快照,峰值降低了10%~20%。

4

5.4 交互事件

另外在手势交互上,对外暴露了各个消息组件的一系列交互事件。常见的场景例如单击浏览消息内容,长按展示消息菜单等。

方案内部提供了基于系统样式的长按菜单,并提供上层菜单配置能力,同时也可以基于暴露的长按手势事件来自定义菜单。

5.5 消息流

一个会话对应一个流,方案也提供了消息流在会话维度上的一些标准化配置。例如消息分页数量、是否自动拉取历史消息、是否开启增量刷新,以及在时间展示上的样式配置等。

此外为了减少列表重绘,消息流也支持增量刷新。通常情况下业务层不需要主动刷新列表,只需对消息数据进行增删改操作,内部会触发对数据源的「diff-update」计算,从而驱动列表的增量更新。

5

6、聊天消息交互层

6.1 概述

对于业务方而言,在消息交互上通常关心这么几点:

  • 1)提供了哪些标准化的交互能力;
  • 2)如何拓展自定义的交互实现;
  • 3)如何对交互流程进行干预。

结合团队现状,我们在方案内部内置了基于某信的IM交互能力,同时定义了相关交互接口,供业务方按需注入实现。

在实际业务中,一个APP内可能存在多个IM场景,因此交互能力支持按会话维度进行注入,各个会话之间的交互是相互隔离的。

6.2 消息源

不同的IM场景,消息数据来源可能存在差异。例如我们私聊、群聊的数据源来自云信数据同步服务,聊天室数据需要通过云信提供的历史消息接口拉取,另外也存在诸如通过业务服务端接口来拉取消息数据的场景。

因此方案上设置了数据源接口 SessionMessageProvider ,提供不同场景消息源的定制能力。

public protocol SessionMessageProvider {

    func messages(in session: Session,

                  anchorMessage: Message?,

                  limit: Int,

                  completion: @escaping ([Message]) -> Void)

}

方案设置了一个负责管理消息数据源的 DataManager 实例, 其依赖 SessionMessageProvider 提供的数据源。同时内置了基于云信的数据源获取实现,能够根据当前会话类型,获取私聊、群聊、聊天室的数据源。

如果当前场景是通过HTTP拉取消息的,则需要业务上层手动注入一个从接口获取数据源的 SessionMessageProvider 实例。

6

6.3 交互源

方案提供了IM标准交互能力,例如消息收发、消息撤回、保存等,以统一各业务交互姿势。

具体的交互源除了要考虑目前包含的云信及业务服务端,也要适应其他交互源,因此将交互实现部分也抽象出了接口 MessageServiceInterface 。业务根据当前实际场景,注入具体的交互实现即可。

下面列出了一些交互申明:

public protocol MessageServiceInterface {

    func send(message: Message, in session: Session, completion: @escaping MessageServiceInterfaceCompletion)

    func resend(message: Message, completion: @escaping MessageServiceInterfaceCompletion)

    func forward(message: Message, to session: Session, completion: @escaping MessageServiceInterfaceCompletion)

    func revoke(message: Message, completion: @escaping MessageServiceInterfaceCompletion)

    func save(message: Message, in session: Session, completion: @escaping MessageServiceInterfaceCompletion)

    func delete(message: Message, completion: @escaping MessageServiceInterfaceCompletion)

}

同样,我们也内置了一些通用交互方案,例如支持云信提供的私聊群聊交互能力,以及由中台提供的通用聊天室服务交互能力,以支持相关场景下快速接入。

7

6.4 交互钩子

在实际IM业务开发过程中,往往需要对交互流程做一些干预,或是在交互过程中做一些定制化的动作。因此方案也提供了一些交互钩子,支持「交互前置校验」、「交互前准备」。

以消息发送流程为例,提供了「发送前校验」、「发送准备」两个消息发送过程的回调钩子:

public protocol MessageServicePrechecker {

   // 消息发送前置校验 

    func shouldSend(message: Message, in session: Session) -> Bool

 

    // ...省略其他代码

}

 

public protocol MessageServicePreparation {

    /// 准备发送准备

    func prepareSend(message: Message, in session: Session, callback: @escaping MessageServicePreparationCallback)

 

    // ...省略其他代码

}

整体的发送流程如图所示:

8

前置校验阶段,用来作消息发送前的校验工作,根据实际状态决定消息是否可以发送。发送准备阶段,则可以在消息投递前做最后的准备工作,例如海外业务可以在这里处理消息资源附件上传Amazon,或是在此处对消息塞入一些客户端信息、反作弊Token等,支持异步操作。

7、业务接入能力

业务只需要在上层提供针对消息以及会话两个维度的配置,就能基于内置的交互能力,构建出一套基础的IM消息流能力。

在具体的消息样式呈现上,则通常需要业务层维护一组关于「消息类型-消息组件类型-消息结构」的映射关系。

具体关联如下:

9

在交互能力上,提供了IM场景的标准能力,业务可以按需使用。

另外,实际IM场景可能需要一些更为丰富的定制能力,则可以依据方案提供的消息数据源接口、消息交互接口来对具体交互实现进行定制。同时也可以使用相关的交互钩子对交互过程进行干预,以适应自己的业务。

8、本文小结

本文对团队IM场景的现状做了简单介绍,撇开具体实现细节,就如何搭建一套能够适应多业务需要的通用IM消息流交互层方案,提供了一些思考和实践经验。

从结果来看,该方案稳定支撑了团队多个IM场景,抹除各场景实现差异,有效降低了维护成本和新业务接入成本。

9、参考资料

[1] 零基础IM开发入门(一):什么是IM聊天系统?

[2] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

[3] 一套原创分布式即时通讯(IM)系统理论架构方案

[4] 从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结

[5] 社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践

[6] 微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

[7] 携程技术分享:亿级流量的办公IM及开放平台技术实践

[8] 百度公共IM系统的Andriod端IM SDK组件架构设计与技术实现

[9] 转转平台IM系统架构设计与实践(一):整体架构设计

[10] 一年撸完百万行代码,企业微信的全新鸿蒙NEXT客户端架构演进之路

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

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

[13] 企业微信针对百万级组织架构的客户端性能优化实践

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

[15] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

[16] 现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

[17] IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

[18] IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践

[19] 阿里技术分享:闲鱼IM基于Flutter的移动端跨端改造实践

[20] IM开发干货分享:万字长文,详解IM“消息“列表卡顿优化实践

[21] IM开发干货分享:IM客户端不同版本兼容运行的技术思路和实践总结

[22] 百度统一socket长连接组件从0到1的技术实践

[23] 淘宝移动端统一网络库的架构演进和弱网优化技术实践

[24] 抖音技术分享:飞鸽IM桌面端基于Rust语言进行重构的技术选型和实践总结

[25] 大型IM工程重构实践:企业微信Android端的重构之路

即时通讯技术学习:

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

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

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

posted @ 2026-04-20 18:43 Jack Jiang 阅读(36) | 评论 (0)编辑 收藏

本文作者阿里云高级技术专家木洛,有优化和修订。

1、前言

IM全称是“Instant Messaging”,中文名是即时通讯。在这个高度信息化的移动互联网时代,生活中IM类产品已经成为必备品,比较有名的如钉钉、微信、QQ等以IM为核心功能的产品。当然目前微信已经成长为一个生态型产品,但其核心功能还是IM。还有一些非以IM系统为核心的应用,最典型的如一些在线游戏、社交应用,IM也是其重要的功能模块。可以说,带有社交属性的应用,IM功能一定是必不可少的。

IM系统在互联网初期即存在,其基础技术架构在这十几年的发展中更新迭代多次,从早期的CS、P2P架构,到现在后台已经演变为一个复杂的分布式系统,涉及移动端、网络、安全和存储等技术的方方面面。其支撑的规模也从早期的少量日活,到现在微信这个巨头最新公布的达到9亿的日活的体量。

IM系统中最核心的是消息系统,消息系统最核心的是消息的同步和存储:

1)消息的同步:将消息完整的、快速的从发送方传递到接收方,就是消息的同步。消息同步系统最重要的衡量指标就是消息传递的实时性、完整性以及能支撑的消息规模。从功能上来说,一般至少要支持在线和离线推送,高级的IM系统还支持“多端同步”;

2)消息的存储:消息存储即消息的持久化保存,这里不是指消息在客户端本地的保存,而是指云端的保存,功能上对应的就是“消息漫游”。“消息漫游”的好处是可以实现账号在任意端登陆查看所有历史消息,这也是高级IM系统特有的功能之一。

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

2、架构设计

本章主要会介绍基于TableStore的现代IM消息系统的架构设计,在详细介绍架构设计之前,会先介绍一种Timeline逻辑模型,来抽象和简化对IM消息同步和存储模型的理解。理解了Timeline模型后,会介绍如何基于此模型对消息的同步以及存储进行建模。基于Timeline模型,在实现消息同步和存储时还会有各方面的技术权衡,例如如何对消息同步常见的读扩散和写扩散两种模型进行对比和选择,以及针对Timeline模型的特征如何来选择底层数据库。

1

 ▲ 上图是消息系统传统架构与现代架构的简单对比

传统架构下,消息是先同步后存储:

对于在线的用户,消息会直接实时同步到在线的接收方,消息同步成功后,并不会进行持久化。而对于离线的用户或者消息无法实时同步成功时,消息会持久化到离线库,当接收方重新连接后,会从离线库拉取所有未读消息。当离线库中的消息成功同步到接收方后,消息会从离线库中删除。传统的消息系统,服务端的主要工作是维护发送方和接收方的连接状态,并提供在线消息同步和离线消息缓存的能力,保证消息一定能够从发送方传递到接收方。服务端不会对消息进行持久化,所以也无法支持消息漫游。

现代架构下,消息是先存储后同步:

先存储后同步的好处是,如果接收方确认接收到了消息,那这条消息一定是已经在云端保存了。并且消息会有两个库来保存,一个是消息存储库,用于全量保存所有会话的消息,主要用于支持消息漫游。另一个是消息同步库,主要用于接收方的多端同步。

消息从发送方发出后,经过服务端转发,服务端会先将消息保存到消息存储库,后保存到消息同步库。完成消息的持久化保存后,对于在线的接收方,会直接选择在线推送。但在线推送并不是一个必须路径,只是一个更优的消息传递路径。

对于在线推送失败或者离线的接收方,会有另外一个统一的消息同步方式。接收方会主动的向服务端拉取所有未同步消息,但接收方何时来同步以及会在哪些端来同步消息对服务端来说是未知的,所以要求服务端必须保存所有需要同步到接收方的消息,这是消息同步库的主要作用。对于新的同步设备,会有消息漫游的需求,这是消息存储库的主要作用,在消息存储库中,可以拉取任意会话的全量历史消息。

以上是传统架构和现代架构的一个简单的对比,现代架构上整个消息的同步和存储流程,并没有变复杂太多,但是其能实现多端同步以及消息漫游。现代架构中最核心的就是两个消息库“消息同步库”和“消息存储库”,是消息同步和存储最核心的基础。而本篇文章接下来的部分,都是围绕这两个库的设计和实现来展开。

3、Timeline模型

在分析“消息同步库”和“消息存储库”的设计和实现之前,在本章会先介绍一个逻辑模型-Timeline。Timeline模型会帮助我们简化对消息同步和存储模型的理解,而消息库的设计和实现也是围绕Timeline的特性和需求来展开。

2

▲ Timeline模型

如图是Timeline模型的一个抽象表述,Timeline可以简单理解为是一个消息队列,但这个消息队列有如下特性:

1)每个消息拥有一个顺序ID(SeqId),在队列后面的消息的SeqId一定比前面的消息的SeqId大,也就是保证SeqId一定是增长的,但是不要求严格递增;

2)新的消息永远在尾部添加,保证新的消息的SeqId永远比已经存在队列中的消息都大;

3)可根据SeqId随机定位到具体的某条消息进行读取,也可以任意读取某个给定范围内的所有消息。

有了这些特性后,消息的同步可以拿Timeline来很简单的实现。图中的例子中,消息发送方是A,消息接收方是B,同时B存在多个接收端,分别是B1、B2和B3。A向B发送消息,消息需要同步到B的多个端,待同步的消息通过一个Timeline来进行交换。A向B发送的所有消息,都会保存在这个Timeline中,B的每个接收端都是独立的从这个Timeline中拉取消息。每个接收端同步完毕后,都会在本地记录下最新同步到的消息的SeqId,即最新的一个位点,作为下次消息同步的起始位点。服务端不会保存各个端的同步状态,各个端均可以在任意时间从任意点开始拉取消息。

消息漫游也是基于Timeline,和消息同步唯一的区别是,消息漫游要求服务端能够对Timeline内的所有数据进行持久化。

基于Timeline,从逻辑模型上能够很简单的理解在服务端如何去实现消息同步和存储,并支持多端同步和消息漫游这些高级功能。落地到实现的难点主要在如何将逻辑模型映射到物理模型,Timeline的实现对数据库会有哪些要求?我们应该选择何种数据库去实现?这些是接下来会讨论到的问题。

4、消息存储模型

3

▲ 基于Timeline的消息存储模型

如图是基于Timeline的消息存储模型,消息存储要求每个会话都对应一个独立的Timeline。如图例子所示,A与B/C/D/E/F均发生了会话,每个会话对应一个独立的Timeline,每个Timeline内存有这个会话中的所有消息,服务端会对每个Timeline进行持久化。服务端能够对所有会话Timeline中的全量消息进行持久化,也就拥有了消息漫游的能力。

5、消息同步模型

消息同步模型会比消息存储模型稍复杂一些,消息的同步一般有读扩散和写扩散两种不同的方式,分别对应不同的Timeline物理模型。

4

▲ 读扩散和写扩散两种不同同步模式下对应的不同的Timeline模型

如图是读扩散和写扩散两种不同同步模式下对应的不同的Timeline模型,按图中的示例,A作为消息接收者,其与B/C/D/E/F发生了会话,每个会话中的新的消息都需要同步到A的某个端,看下读扩散和写扩散两种模式下消息如何做同步。

读扩散:

消息存储模型中,每个会话的Timeline中保存了这个会话的全量消息。读扩散的消息同步模式下,每个会话中产生的新的消息,只需要写一次到其用于存储的Timeline中,接收端从这个Timeline中拉取新的消息。

优点是消息只需要写一次,相比写扩散的模式,能够大大降低消息写入次数,特别是在群消息这种场景下。但其缺点也比较明显,接收端去同步消息的逻辑会相对复杂和低效。接收端需要对每个会话都拉取一次才能获取全部消息,读被大大的放大,并且会产生很多无效的读,因为并不是每个会话都会有新消息产生。

写扩散:

写扩散的消息同步模式,需要有一个额外的Timeline来专门用于消息同步,通常是每个接收端都会拥有一个独立的同步Timeline,用于存放需要向这个接收端同步的所有消息。

每个会话中的消息,会产生多次写,除了写入用于消息存储的会话Timeline,还需要写入需要同步到的接收端的同步Timeline。在个人与个人的会话中,消息会被额外写两次,除了写入这个会话的存储Timeline,还需要写入参与这个会话的两个接收者的同步Timeline。而在群这个场景下,写入会被更加的放大,如果这个群拥有N个参与者,那每条消息都需要额外的写N次。

写扩散同步模式的优点是,在接收端消息同步逻辑会非常简单,只需要从其同步Timeline中读取一次即可,大大降低了消息同步所需的读的压力。其缺点就是消息写入会被放大,特别是针对群这种场景。

在IM这种应用场景下,通常会选择写扩散这种消息同步模式。

IM场景下,一条消息只会产生一次,但是会被读取多次,是典型的读多写少的场景,消息的读写比例大概是10:1。若使用读扩散同步模式,整个系统的读写比例会被放大到100:1。

一个优化的好的系统,必须从设计上去平衡这种读写压力,避免读或写任意一维触碰到天花板。所以IM系统这类场景下,通常会应用写扩散这种同步模式,来平衡读和写,将100:1的读写比例平衡到30:30。

当然写扩散这种同步模式,还需要处理一些极端场景,例如万人大群。针对这种极端写扩散的场景,会退化到使用读扩散。一个简单的IM系统,通常会在产品层面限制这种大群的存在,而对于一个高级的IM系统,会采用读写扩散混合的同步模式,来满足这类产品的需求。采用混合模式,会根据数据的不同类型和不同的读写负载,来决定用写扩散还是读扩散。

6、典型架构设计

8

如上图所示,是一个典型的消息系统架构。

该典型的消息系统架构中包含几个重要组件:

1)端:作为消息的发送和接收端,通过连接消息服务器来发送和接收消息。

2)消息服务器:一组无状态的服务器,可水平扩展,处理消息的发送和接收请求,连接后端消息系统。

3)消息队列:新写入消息的缓冲队列,消息系统的前置消息存储,用于削峰填谷以及异步消费。

4)消息处理:一组无状态的消费处理服务器,用于异步消费消息队列中的消息数据,处理消息的持久化和写扩散同步。

5)消息存储和索引库:持久化存储消息,每个会话对应一个 Timeline 进行消息存储,存储的消息建立索引来实现消息检索。

6)消息同步库:

写扩散形式同步消息,每个用户的收件箱对应一个 Timeline,同步库内消息不需要永久保存,通常对消息设定一个生命周期。

新消息会由端发出,通常消息体中会携带消息 ID(用于去重)、逻辑时间戳(用于排序)、消息类型(控制消息、图片消息或者文本消息等)、消息体等内容。

消息会先写入消息队列,作为底层存储的一个临时缓冲区。消息队列中的消息会由消息处理服务器消费,可以允许乱序消费。消息处理服务器对消息先存储后同步,先写入发件箱 Timeline(存储库),后写扩散至各个接收端的收件箱(同步库)。

消息数据写入存储库后,会被近实时的构建索引,索引包括文本消息的全文索引以及多字段索引(发送方、消息类型等)。

对于在线的设备,可以由消息服务器主动推送至在线设备端。对于离线设备,登录后会主动向服务端同步消息。每个设备会在本地保留有最新一条消息的顺序 ID,向服务端同步该顺序 ID 后的所有消息。

7、消息库设计

基于Timeline模型,以及Timeline模型在消息存储和消息同步的应用,我们看下消息同步库和消息存储库的设计。

6
▲ 基于Timeline的消息库设计

消息同步库:

消息同步库用于存储所有用于消息同步的Timeline,每个Timeline对应一个接收端,主要用作写扩散模式的消息同步。

这个库不需要永久保留所有需要同步的消息,因为消息在同步到所有端后其生命周期就可以结束,就可以被回收。但是如前面所介绍的,一个实现简单的多端同步消息系统,在服务端不会保存有所有端的同步状态,而是依赖端自己主动来做同步。

所以服务端不知道消息何时可以回收,通常的做法是为这个库里的消息设定一个固定的生命周期,例如一周或者一个月,生命周期结束可被淘汰。

消息存储库:

消息存储库用于存储所有会话的Timeline,每个Timeline包含了一个会话中的所有消息。这个库主要用于消息漫游时拉取某个会话的所有历史消息,也用于读扩散模式的消息同步。

消息同步库和消息存储库,对数据库有不同的要求,如何对数据库做选型,在下面会讨论。

8、数据库选型

消息系统最核心的两个库是消息同步库和消息存储库,两个库对数据库有不同的要求:

7

总结下来,对数据库的要求有如下几点:

  • 1)表结构设计能够满足Timeline模型的功能要求:不要求关系模型,能够实现队列模型,并能够支持生成自增的SeqId;
  • 2)能够支持高并发写和范围读,规模在十万级TPS;
  • 3)能够保存海量数据,百TB级;
  • 4)能够为数据定义生命周期。

9、本文小结

本文主要介绍了现代IM系统中消息推送和存储架构的实现,基于逻辑的Timeline模型,我们可以很清晰明了的理解整个消息推送和存储的架构。而基于Timeline的消息存储和推送模型,其实不光可以应用在IM消息系统中,还可应用在例如Feeds流、实时消息同步、直播弹幕等场景。

10、参考资料

[1] 浅谈IM系统的架构设计

[2] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端

[3] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

[4] 一套原创分布式即时通讯(IM)系统理论架构方案

[5] 从零到卓越:京东客服即时通讯系统的技术架构演进历程

[6] 蘑菇街即时通讯/IM服务器开发之架构选择

[7] 腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

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

[9] 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

[10] 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

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

[12] 社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

[13] 社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

[14] 从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

[15] 瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)

[16] 阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

[17] IM开发基础知识补课(十):大型IM系统有多难?万字长文,搞懂异地多活!

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

[19] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[20] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[22] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[23] IM开发技术学习:揭秘微信朋友圈这种信息推流背后的系统设计

[24] 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路

[25] 基于实践:一套百万消息量小规模IM系统技术要点总结

[26] 跟着源码学IM(十):基于Netty,搭建高性能IM集群(含技术思路+源码)

[27] 一套十万级TPS的IM综合消息系统的架构实践与思考

[28] 直播系统聊天技术(八):vivo直播系统中IM消息模块的架构实践

[29] 得物从0到1自研客服IM系统的技术实践之路

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

[31] 企业微信针对百万级组织架构的客户端性能优化实践

[32] 一套分布式IM即时通讯系统的技术选型和架构设计

[33] 陌陌技术分享:陌陌IM在后端KV缓存架构上的技术实践

[34] 微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

[35] 携程技术分享:亿级流量的办公IM及开放平台技术实践

[36] 转转平台IM系统架构设计与实践(一):整体架构设计

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

[38] 一年撸完百万行代码,企业微信的全新鸿蒙NEXT客户端架构演进之路

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

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

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

[42] 新手入门一篇就够:从零开发移动端IM

[43] 移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

[44] 零基础IM开发入门(一):什么是IM聊天系统?

即时通讯技术学习:

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

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

本文内容引用自:http://www.52im.net/thread-1230-1-1.html

posted @ 2026-04-14 11:39 Jack Jiang 阅读(86) | 评论 (0)编辑 收藏

本文由vivo 互联网服务器团队Deng Qian分享,有排版和内容优化。

1、引言

在了解加密原理前,我们来看看这样一个故事:

小红和小明是情侣,一天,小红给小明发短信说:“亲爱的,我银行卡上没有钱了,你给我转1万块钱吧。”有过上当受骗经历的人都知道这有可能是小偷偷了小红手提包,然后拿手机发的短信。

不过我们小明学过加密原理,于是他回复说:“你直接拿我的银行卡刷吧,密码加上我们第一次约会的日期就是663156。”

很明显,只有小明和小红知道他们第一次约会是什么时候,假设是2008年4月1号,那么小红就可以根据计算663156-200841=462315得到银行卡密码,就可以消费了。

这就是加密的本质:将信息与密钥相加得到加密后的信息,只有知道密钥的人才能解密。

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

cover-opti

2、系列文章

  1. 即时通讯安全篇(一):正确地理解和使用Android端加密算法
  2. 即时通讯安全篇(二):探讨组合加密算法在IM中的应用
  3. 即时通讯安全篇(三):常用加解密算法与通讯安全讲解
  4. 即时通讯安全篇(四):实例分析Android中密钥硬编码的风险
  5. 即时通讯安全篇(五):对称加密技术在Android平台上的应用实践
  6. 即时通讯安全篇(六):非对称加密技术的原理与应用实践
  7. 即时通讯安全篇(七):用JWT技术解决IM系统Socket长连接的身份认证痛点
  8. 即时通讯安全篇(八):如果这样来理解HTTPS原理,一篇就够了
  9. 即时通讯安全篇(九):你知道,HTTPS用的是对称加密还是非对称加密?
  10. 即时通讯安全篇(十):为什么要用HTTPS?深入浅出,探密短连接的安全性
  11. 即时通讯安全篇(十一):IM聊天系统安全手段之通信连接层加密技术
  12. 即时通讯安全篇(十二):IM聊天系统安全手段之传输内容端到端加密技术
  13. 即时通讯安全篇(十三):信创必学,一文读懂什么是国密算法
  14. 即时通讯安全篇(十四):网络端口的安全防护技术实践
  15. 即时通讯安全篇(十五):详解硬编码密码的泄漏风险及其扫描原理和工具
  16. 即时通讯安全篇(十六):对称加密 vs 非对称加密?一文搞懂!》(☜ 本文

3、什么是秘钥

1

既然加密需要密钥,那么密钥是什么呢?

密钥是作用于加密时的一串密码,通过密钥进行信息加密,传输,到达接收者和监听者,由于接收者也有密钥,所以接收者可以根据密钥进行解密。从而防止通讯信息泄露。

4、什么是对称加密

2

前言讲的故事就是一个对称式加密,小明和小红都知道第一次约会的日期。所以传统的对称式加密需要通讯双方都保存同一份密钥,通过这份密钥进行加密和解密。所以对称加密也称为单密钥加密。

对称加密的优势在于加解密速度快,但是安全性较低,密钥一旦泄露,所有的加密信息都会被破解。同时密钥的传输和保密也成为难题。为了解决密钥传输的问题,出现通过密钥交换建立共享密钥的技术。具体如何建立共享密钥呢?我们往下看。

5、对称加密之建立共享密匙

在小明、小红和小偷的三人世界中,由于小明是学过加密原理的,知道迪菲–赫尔曼密钥交换(Diffie-Hellman Key Exchange),所以他知道如何建立共享密钥。

5.1 颜料混合把戏

接下来我们看看如何通过颜料混合把戏建立共享密钥吧。

假设在房间中有小明、小红和小偷三个人,每个人各自拥有相同颜色的颜料。在房间的正中间也有这些颜料。接下来,小明要和小红建立共享密钥了。

此时,小明对大家说:“我要用蓝色。”然后小明从自己的颜料里选择了黄色,这个黄色就是小明的私钥,小红和小偷都不知道。

小明将自己的私钥黄色与公钥蓝色混合后,得到了一种不能分解的颜色,我们就叫“小明-蓝色”吧(虽然大家都知道黄+蓝变绿,但是这里我们为了知道是谁的混合色,还是以名字加公钥颜色来称呼),然后小明将“小明-蓝色”公布了出来。

同样,小红听到了小明说用蓝色后,也选择了自己的私钥红色与公钥蓝色混合,得到了“小红-蓝色”并公布了出来。

此时,房间中小明、小红、小偷三人都知道了几个信息:

  • 1)他们都用了蓝色;
  • 2)小明公布了“小明-蓝色”(小红和小偷不知道是什么颜料与蓝色的混合);
  • 3)小红公布了“小红-蓝色”(小红和小偷不知道是什么颜料与蓝色的混合)。

接下来,见证奇迹的时刻到了:小明拿到“小红-蓝色”与自己的私钥“黄色”混合,得到“小红-蓝色-小明”的新颜料。同样的,小红拿到“小明-蓝色”与自己的私钥“红色”混合,得到“小明-蓝色-小红”。大家发现了吗?“小红-蓝色-小明”和“小明-蓝色-小红”是一模一样的颜色。而小偷不知道小明和小红的私钥颜色,无法混合出与他们相同的颜色。

至此,共享密钥建立起来了。在了解了共享密钥的建立过程后,我们将告别实体颜料,采用数字的方式来建立共享密钥。

注:大家可能想到了,小偷可以根据自己的颜料与公钥“蓝色”混合,尝试得出“小明-蓝色”和“小红-蓝色”。这样的方法称之为穷举法,也就是尝试所有的可能性,进行信息破解,所以加密算法在理论上都是可以通过穷举法破解的,只不过实际上,超级计算机都需要计算万亿年才能穷举出所有可能性。

5.2 乘法把戏

首先,我们假设乘法如同颜料混合一样,是不能分解的,看看如何用乘法与数字建立共享密钥。

小明公开了一个数字5,然后小明选择了一个私人数字4,然后利用乘法将两者混合起来,得到“小明-5”(20),接下来小红也选择了一个私人数字7得到“小红-5”(35),小明拿到35*4=140,小红拿到20*7=140。共享密钥建立完成。

大家也发现了,小偷知道20,35,5这三个数字后,用除法就能算出小明和小红的私钥。所以,接下来我们将了解实际使用中的如何使用乘法把戏来防止私钥被计算出来的。

6、对称加密之迪菲·赫尔曼密钥交换算法

我们都知道幂运算,但是要让计算机计算就比较难了。所以,我们会用幂运算作为建立共享密钥的乘法把戏。同时,我们还要了解钟算的原理,这里的钟可以理解成我们经常看到的时钟,我们常见的时钟最大是12,如果当前是10点,过了4个小时后,就变成了下午2点。也就是(10+4)mod12=2。了解了钟算和幂运算后,就开始进入正题吧。

还是小明、小红和小偷的房间,小明声明了钟为11,幂运算的底为2,接下来小明和小红分别选择了自己的私钥4和7。

1)第一步:小明混合自己的“小明-11,2”得到,小红混合自己的“小红-11,2”得到。

2)第二步:小明拿到“小红-11,2”(7)进行计算,小红拿到“小明-11,2”(5)进行计算。

大家注意到了吗:小明和小红建立了共享密钥3,而小偷无法根据已知的11、2、5、7这几个数字计算出密钥或小明小红的私钥。有了共享密钥后,小明和小红就可以安全进行加密传输了。

迪菲-赫尔曼密钥交换:

3

7、 对称加密之AES加密过程

AES 的全称是 Advanced Encryption Standard ,是最流行的对称加密算法,其加解密速度快。

AES支持128位,192位,256位三种长度的密钥,密钥越长安全性越高。AES加密时会把明文切分成许多小块的明文,然后对每块明文单独加密,将加密后的密文传送出去,接收方再将密文切块解密,得到明文。如下图所示。

AES加密原理:

4

上一步中小明和小红已经协商好了密钥3。接下来就可以通过对称加密进行通信了。

在小明、小红和小偷的房间中,小明想把密码“462315”告诉小红,于是:

1)第一步:将密码按照一位的长度进行切分(实际中通常按128位进行切分);就变成了“4”“6”“2”“3”“1”“5”;

2)第二步:对每块明文通过密钥3进行加密,结果就是“795648”,然后小明告诉小红和小偷:“我的密码是795648”;

3)第三步:小红拿到密文后,对密文进行切块,对每块通过密钥3进行解密,就得到了正确的密码“462315”,而小偷由于不知道密钥,就无法解密出正确的信息。

8、什么是非对称加密

8.1 概述

在对称加密中,加密和解密使用的是同一份密钥。所以,在非对称加密中,加密和解密使用的是不同的密钥。

非对称加密中的密钥分为公钥和私钥。公钥顾名思义就是公开的,任何人都可以通过公钥进行信息加密,但是只有用户私钥的人才能完成信息解密。非对称加密带来了一个好处,避免了对称式加密需要传输和保存同一份密钥的痛苦。

现在最流行的非对称加密算法就是RSA加密算法,具体是怎么做的呢,我们继续往下看。

8.2 RSA加密过程

百科是这么解释的:

RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA 就是他们三人姓氏开头字母拼在一起组成的。

5

▲ RSA算法的作者合影(照片拍摄于1978年)

前面我们讲了如何通过钟算和幂函数建立不可逆(计算机可以通过穷举法计算出私钥,实际场景中就算是超级计算机也要计算几万亿年之久)的共享密钥。由于小红是小明的女朋友,小明天天在小红面前给她讲RSA加密算法的原理,所以小红也知道怎么得出自己的公钥和私钥。

接下来我们一起跟着小红的脚步,看看RSA加密的公钥和私钥是怎么计算出来的:

  • 1)第一步:小红选择了两个很大的质数p和q,这里为了便于计算,选择2和11;
  • 2)第二步:计算p和q的乘积n=p*q=2*11=22;
  • 3)第三部:计算n的欧拉函数φ(n)=(p-1)*(q-1)=10;
  • 4)第四步:选择一个小于φ(n)且与φ(n)互质的整数e,{1,3,7,9},这里选择e=7;
  • 5)第五步:计算e对于φ(n)的模反元素(ed mode φ(n) = 1)d,d=3。

到这里小红就得到了他自己的公钥(n,e)和私钥(n,d)。其中n就是钟大小,e和d就是幂函数的幂。接下来就通过计算出来的公钥和私钥进行数据的加解密。

还是小明、小红和小偷三个人,小红对大家说,我的公钥是(22,7),小明知道了小红的公钥后,想讲自己的信息“14”告诉小红,于是就用小红公开的公钥进行加密。

具体步骤如下:

  • 1)第一步:小明根据要加密的信息14进行计算,得到加密后的信息20,然后将20告诉小红和小偷;
  • 2)第二步:小红有自己的私钥,将加密信息20进行解密,,得到了小明想传递给小红的信息。而小偷呢,知道22,7,20,但是不知道小红的密钥(22,3),无法解密出正确的信息。

RSA加密算法在数字签名中也发挥着巨大的作用:假设小偷可以假冒小红,说小红的公钥是(22,9),而小明不知道是小偷假扮的,按照小偷的公钥加密后,结果被小偷解密了。数字签名的作用就是防止信息被篡改,小红说她的公钥是(22,7)的同时,使用私钥给这段信息(通常使用MD5值计算签名)加上签名,小明得到公钥(22,7)和签名13,小明拿到签名后利用公钥计算出信息是否被篡改。

6

9、加密的实际作用

7

本文使用的很小的数来进行加密原理的讲解,为了是读者可以方便进行计算。

在实际使用中(n,e)都是特别大的数,其中n的长度都在768以上,1024长度被认为是基本安全的。

(1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413=

33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489

×

36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917)

10、写在最后

8

或许看到这里,大家心里还有许多疑惑:

  • 1)为什么小明和小红建立共享密钥时,通过几次幂运算和钟算就能得到一样的共享密钥?
  • 2)为什么RSA加密算法要用两个质数?
  • 3)为什么通过公钥加密的信息可以通过私钥解开?

加密算法的背后,是一道道迷人的数学难题。而RSA加密算法之所以被广泛运用,是因为一个名为整数分解的古老数学问题,你可以轻易找到两个很大的质数相乘得到一个结果n,但是要将这个结果n分解回两个质数就变得极其困难。尽管这个所谓的“整数分解”问题被研究了数个世纪,还没人能找到一个足够高效的通用方法解决它,并对标准RSA钟大小造成危害。

数学史中充满了未解决的问题,尽管这些迷人的问题缺乏任何实际应用,却单靠其美学特质就吸引了数学家进行深入探究。

令人颇感惊讶的是,许多这类迷人但显然无用的问题后来都有了很大的实用价值,这一价值只有在问题被研究数个世纪后才得以破解。整数分解这一问题由来已久。对其最早的严肃研究似乎是在17世纪,由数学家费马(Fermat)和梅森(Mersenne)进行的。欧拉(Euler)和高斯(Gauss)两位数学“泰斗”也在接下来的世纪里对这一问题做出了贡献。但直到公钥加密于20世纪70年代被发明,分解大数字的困难才成为一个实际应用的关键。

11、本文小结

最后总结一下。

首先:我们通过一个诈骗短信的例子,引出了加密的原理就是信息+密钥,密钥就是对信息进行加解密的一串数字。

然后:通过颜料混合把戏形象的演示了如何建立共享密钥。在使用乘法建立共享密钥的过程中,学习了钟算和幂运算,接着我们了解了RSA加密算法的过程,通过两个质数生成公钥和私钥。

最后:我们根据公钥进行信息加密,再通过私钥完成信息解密。

12、参考资料

[1] 探讨组合加密算法在IM中的应用

[2] 一文读懂常用加解密算法与网络通讯安全

[3] 非对称加密技术的原理与应用实践

[4] 你知道,HTTPS用的是对称加密还是非对称加密?

[5] IM聊天系统安全手段之通信连接层加密技术

[6] IM聊天系统安全手段之传输内容端到端加密技术

[7] 信创必学,一文读懂什么是国密算法的

[8] 传输层安全协议SSL/TLS的Java平台实现简介和Demo演示

[9] 理论联系实际:一套典型的IM通信协议设计详解(含安全层设计)

[10] 微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解

[11] 来自阿里OpenIM:打造安全可靠即时通讯服务的技术实践分享

[12] 简述实时音视频聊天中端到端加密(E2EE)的工作原理

[13] 移动端安全通信的利器——端到端加密(E2EE)技术详解

[14] 通俗易懂:一篇掌握即时通讯的消息传输安全原理

[15] 一分钟理解 HTTPS 到底解决了什么问题

[16] 一篇读懂HTTPS:加密原理、安全逻辑、数字证书等

[17] 基于Netty的IM聊天加密技术学习:一文理清常见的加密概念、术语等

[18] 手把手教你为基于Netty的IM生成自签名SSL/TLS证书

[19] 即时通讯初学者必知必会的20个网络编程和通信安全知识点

[20] 零基础IM开发入门(五):什么是IM系统的端到端加密?

[21] 微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

[22] 零基础IM开发入门(一):什么是IM系统?

[23] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[24] 转转平台IM系统架构设计与实践(一):整体架构设计

[25] 一套分布式IM即时通讯系统的技术选型和架构设计

即时通讯技术学习:

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

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

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

posted @ 2026-04-09 12:29 Jack Jiang 阅读(50) | 评论 (0)编辑 收藏

本文由悟空聊架构分享,有修订和排版优化。

1、引言

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

cover-opti

2、系列文章

本文是该系列文章中的第 5 篇:

  1. 网络编程入门如此简单(一):假如你来设计网络,会怎么做?
  2. 网络编程入门如此简单(二):假如你来设计TCP协议,会怎么做?
  3. 网络编程入门如此简单(三):什么是IPv6?漫画式图文,一篇即懂!
  4. 网络编程入门如此简单(四):一文搞懂localhost和127.0.0.1
  5. 网络编程入门如此简单(五):UDP跟TCP相比,到底差了什么?》(* 本文

3、写在前面

1、背景

本题是我在面试中,技术总监问我的一道真题,当时答得不太好,所以把它揪出来总结了下。后来问了下总监,总监说这是阿里的面试题。。

其实面试官主要是想让我说出 UDP 和 TCP 的原理上的区别,怎么给 UDP 加些功能实现 TCP。

看好去很容易就能说出一两个 TCP 和 UDP 的区别,但如果能用女朋友都能听懂的方式该怎么说呢?

女朋友:我不想听课本上讲的!我听不懂呀~

下面我会以大白话的方式来解答上面的问题。

4、UDP协议的主要特点

2、UDP-的特点

UDP 让我想起了刚毕业参加工作那会,一名毕业菜鸟。

1)沟通简单:

领导安排的任务,直接干就完了。

UDP 也是,相信网络世界永远是美好的,我发送的包是很容易送到的,接收方也是很容易组装的。数据结构也很简单,不需要大量的数据结构、处理逻辑、包头字段。

2)轻信他人:

测试人员报的 bug 我也不会和她争论什么,永远相信测试人员是对的,测试人员说啥就是啥,我改就是。

UDP 也是,不会建立连接,有个端口号,谁都可以监听这个端口号往上面发数据。也可以从这个端口号传给任何人数据。反正我只管发就是。

3)不会讨价还价:

产品经理昨天说手机壳需要根据心情变色,测试人员说这个 bug 要把关联的两个 bug 一起修掉。那就按照他们说的做吧!

UDP 也是,不懂坚持和退让。也就是根据网络情况进行拥塞控制。无论网络丢包多严重,我还是照样发~

5、UDP协议的使用场景

3、UDP-使用场景

针对像我那时候毕业菜鸟的情况,领导给我安排了三种工作环境让我选。

1)内部系统,任务简单,模块单一,不需要考虑代码的关联影响,即使失败了也没有关系。

UDP 也是,需要资源少,网络情况比较好的内网,或者对于丢包不敏感的应用。

2)有一个强力的团队支持,都是中高级开发、测试人员,团队成员打过很多年交道,互相信任。有什么问题,吼一嗓子就可以了!

UDP 也是,不需要一对一沟通来建立连接,可以广播的应用。

3)一个新项目,需要有激情,对于刚毕业的菜鸟,都是有很强的自主能动性的,也不会耍滑头,躲在厕所玩手机,带薪拉shi ?即使项目不忙,我也抓紧时间干。项目忙,还是一样干!

UDP 也是,猛着发包就是,主要应用在需要处理速度快,时延低,可以容忍少数丢包的情况。即使网络情况不佳,发包就是~

针对上面的三大场景,UDP 常用在实时竞技游戏,IoT 物联网,移动通信领域。

6、TCP协议的主要特点  

4、TCP-的特点?

6.1 面向连接

TCP 和 UDP 是传输层里面比较重要的两个协议。大部分面试的时候都会问到两者的区别。而大部分都会两句,比如 TCP 是面向连接的,UDP 是面向无连接。

那什么是面向连接?

TCP 三次握手是我们常常念叨和背诵的。而在这三次握手成功后,就是建立连接成功。

那什么又叫面向呢?

我们也常听到面向对象编程、面向切面编程、面向服务编程。那到底什么是面向?

在我看来 面向 就是遵循一定的协议、规范、数据结构等来做一系列事情。

比如面向连接,就是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,用这样的数据来保证所谓的面向连接的特性。

知道了 TCP 的是用三次握手来建立连接,那我们是否可以让 UDP 也发三个包来模拟 TCP 建立连接?可以是可以,但是如果只是建立,而不是面向连接,其实意义不大。

那 TCP 面向连接做了哪些事情?

TCP 提供可靠交付,通过 TCP 连接传输的数据,可以无差错、不丢失、不重复、并且按序到达。而 UDP 继承了 IP 包的特性,不保证不丢失,不保证按顺序到达。

6.2 面向字节流

TCP 是面向字节流,所谓字节流,就是发的是一个流,没头没尾。TCP 自己维护流状态。

UDP 基于 IP 数据报,一个一个地发,一个一个地收。

6.3 拥塞控制

TCP 拥有拥塞控制,如果包丢弃了或者网络环境不好了,就会根据网络情况自行控制自己的行为,看下是发快点还是发慢点。

UDP 则没有这么智能了, 你让我发,我就发呗,反正是你让我发的,其他的一概不管~

6.4 有状态服务

TCP 是一个有状态的服务,有状态可以理解为:我记录了哪些发送了,哪些没有发送,哪些接收到了,哪些没接收到,应该接收哪个了,一点差错都不行。TCP 干的事情可真多!

而 UDP 则不是有状态的服务,我只管发,其他的就交给接收端吧,有点任性是吧?

7、如何让UDP追上TCP的能力?

建立连接上面已经讲到了,三次握手和四次握手,UDP 也可以模拟去做。

那下面还有几个问题:

  • 1)顺序问题;
  • 2)丢包问题;
  • 3)流量控制;
  • 4)拥塞控制。

TCP 的数据结构长这样:

5

其实如果你能把这些结构讲清楚,就已经理解了 TCP 的核心功能。下面我还是用大白话的方式来讲解上面的四个问题。

顺序问题和丢包问题可以利用确认与重发的机制。假如包收到了,可以做一个确认,发送一个 ACK 给发送端,告诉他我收到了。假如有的包提前到了,就缓存着。假如有包丢失了,就可以超时重试。超时重试不宜过短,时间必须大于往返时间 RTT,否则会引起不必要的重传。也不宜过长,如果超时时间过长,访问就变慢了。那怎么确定这个时间,可以通过采样 RTT 的时间,进行加权平均。还需要根据网络状况,动态变化。可以了解下自适应重传算法。

流量控制就是根据网络情况调整发包的速率。利用的是滑动窗口。在对于包的确认中,同时会携带一个窗口的大小,只要利用好这个窗口大小,就能很好地调整发包速率,发的报文段不要超过窗口的大小就 OK。

6

拥塞控制主要用来避免包丢失和超时重传,如果出现了这两种现象,就说明发的速率太快了。那最开始怎么知道发送速率呢?其实开始时只发送一个报文段数据,如果收到一个确认,则倍增报文段,依次类推。当发现超时重传时,就又回到只发送一个报文段的情况,这个就是慢启动,这种方式不合适。其实还有一种快速重传算法,简单来说就是拥塞窗口减半,后续线性增速。针对于算法怎么实现的,这里就不展开讲述了。

7

至此,我用大白话的方式讲解了 UDP 和 TCP 的区别,以及 UDP 缺什么功能,需要怎么去弥补才能实现 TCP 的功能。相信这样回答的思路可以让面试官觉得还是有点东西的。

8、参考资料

[1] TCP/IP详解 - 第11章·UDP:用户数据报协议

[2] TCP/IP详解 - 第17章·TCP:传输控制协议

[3] 通俗易懂-深入理解TCP协议(上):理论基础

[4] 通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理

[5] 快速理解TCP协议一篇就够

[6] 快速理解TCP和UDP的差异

[7] 快速理解为什么说UDP有时比TCP更有优势

[8] 一泡尿的时间,快速搞懂TCP和UDP的区别

[9] 跟着动画来学TCP三次握手和四次挥手

[10] 假如你来设计网络,会怎么做?

[11] 假如你来设计TCP协议,会怎么做?

[12] 深入地理解UDP协议并用好它

[13] 如何让不可靠的UDP变的可靠?

[14] UDP比TCP高效?还真不一定!

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

[16] 为何基于TCP协议的移动端IM仍然需要心跳保活机制?

[17] 技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解

即时通讯技术学习:

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

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

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

posted @ 2026-03-17 15:35 Jack Jiang 阅读(52) | 评论 (0)编辑 收藏

     摘要: 本文由网易云音乐技术团队入云分享,有修订和排版优化。1、引言说起 IM,大家应该都或多或少了解过一些,一般被熟知是在一些聊天场景里应用的比较多;而一般情况下我们常接触的业务中大多是做一些接口的查询提交之类的操作,用正常的 Ajax 请求就足以满足需求,比较难接触到 IM 这种方案。但如果涉及到一些需要频繁更新数据的业务场景,使用常规接口查询难免会给服务端造成比较大的性能开销,并且数据更新的延迟也会...  阅读全文

posted @ 2026-03-02 21:56 Jack Jiang 阅读(62) | 评论 (0)编辑 收藏

1、基本介绍

logo

RainbowTalk 是一套基于 MobileIMSDK 开源通信框架的产品级纯血鸿蒙NEXT端IM系统。RainbowTalk与姊妹产品 RainbowChat技术同源 ,不同于市面上某些开源或售卖的demo级代码,RainbowChat已被成千上万真实的客户使用过,解决了大量的产品逻辑、代码逻辑、细节优化等问题。

RainbowTalk 由纯ArkTS编写、全新开发,没有套壳、也没走捷径,原生“纯血”(详见:《RainbowTalk详细介绍)。

RainbowTalk 无闭源代码(包括核心通信层),这与市面上知识产权来路不明、无核心技术、无售后的“三无”产品,或打着开源名义实则闪烁其词不开源核心的产品有本质区别。

RainbowTalk 是 RainbowChat 和 RainbowChat-Web 的姊妹产品。

2

☞ 详细介绍:http://www.52im.net/thread-4822-1-1.html
☞ 运行截图:http://www.52im.net/thread-4824-1-1.html (运行视频
☞ 下载体验:http://www.52im.net/thread-4825-1-1.html

2、关于MobileIMSDK开源框架

mb_logo_opti

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

工程同步开源地址:

3、功能情况

1)支持文本消息、语音留言消息、图片消息、大文件消息(支持断点上传)、短视频消息、个人名片、群名片、Emoji表情、消息撤回、消息转发、消息引用、“@”功能、“扫一扫”功能等;
2)支持一对一陌生人聊天模式;
3)支持一对一正式好友聊天模式;
4)支持多对多群聊聊天模式;
5)完善的群组信息管理:建群、退群、解散、转让、邀请、踢人、群公告等;
6)完整的注册、登陆(同时支持手机验证码登录和密码登录)、密码找回等功能闭环;
7)个人中心功能:改基本信息、改个性签名、改头像、改密码等;
8)支持个人相册查看;
9)完整的离线消息/指令拉取机制;
10)完整的本地消息/指令缓存机制,节省网络流量;
11)完整的富媒体文件(语音、大文件、图片、短视频)缓存机制,节省网络流量;
12)完整的好友关系管理:查找好友、发出请求、处理请求、删除好友、好友备注等;
13)其它未提及的功能和特性请自行下载体验

RainbowTalk线上版本目前仅作演示和研究之用,运行环境配置最小化(仅1核1G和1MB带宽),请客观评估。

4、技术亮点

1)与姊妹产品RainbowChat 技术同源(算法和功能逻辑历经时间考验和大量客户面辐射,可靠性一定优于短时间内堆砌功能的产品);
2)通信底层到上层功能,完全自主开发——版权清晰、技术资产可控;
3)超轻量级——纯ArkTS编写且无任何重依赖;
4)通讯核心层基于MobileIMSDK 工程,保证了业务代码与通信核心的高度分层(经验不足的IM产品是做不到这一点的);
5)支持完整的消息送达保证(QoS)机制,保证送达率,理论丢包率约为0.0001%;
6)基于 MobileIMSDK 工程的自有协议,未来的流量压缩对于APP端的节电控制和流量控制、服务端的网络吞吐等都有完全的控制能力;
7)完善的网络状况自动检测、断网重连等服务自动治愈能力;
8)核心通信算法和实现均为自主原创(历经10年,并非开源拼凑),保证了技术的持续改进、升级、扩展;
9)聊天协议兼容和互通:实现了与姊妹产品RainbowChatRainbowChat-Web的完全兼容和消息互通;

5、技术原则

为了更易学习、研究、2次开发,RainbowTalk始终遵从:

1)界面与通信解偶:UI界面与网络通信层和数据处理层代码解耦,UI界面的重构、维护、改版都非常容易和优雅;

3)核心内聚和收敛:得益于长期的提炼和经验积累,网络通信核心层高度封装,开发者无需理解复杂网络算法。

4)纯 ArkTS 实现:纯ArkTS编写,无重量级框架和库依赖(更无Native代码),可干净利落地对接各种既有系统;

5)跨平台运行能力:受益于鸿蒙系统的跨端特性,理论上本应用的客户端可运行于任何支持鸿蒙Next的平台上;

6)架构设计简洁:简单直接,易于学习,能少一个分层则绝不强行炫技;

7)简单地就是最好的:始终贯彻简单直接的互联网产品技术理念。

6、主要功能运行截图

(☞ 更多运行截图 、更多运行视频 、详细介绍 ☜)

all-in-one-拼合总图-v2.6(导出35size90pct)_3比4

本文内容引用自:http://www.52im.net/thread-4822-1-1.html) 

posted @ 2026-02-25 17:47 Jack Jiang 阅读(52) | 评论 (0)编辑 收藏

本文引用了45岁老架构师尼恩的技术分享,有修订和重新排版。

1、引言

接上篇《如何保障分布式IM聊天系统的消息有序性(即消息不乱)》,本文主要聚焦分布式IM聊天系统消息可靠性问题,即如何保证消息不丢失。

cover-2-opti

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。

本文是2篇文章中的第 1 篇:

本篇主要聚焦的是分布式IM聊天系统消息可靠性问题。

3、痛点拆解:聊天消息总是丢?不是网络差,是设计没兜底

产品做着做着,用户开始投诉:“我明明发了消息,对方怎么没收到?”。你查日志发现——消息真丢了。但更可怕的是:你也不知道它什么时候丢的。

这背后,其实是移动场景下的经典三连击:

  • 1)地铁进隧道,网络闪断;
  • 2)App 被系统杀掉,进程没了;
  • 3)对方服务器刚好在发布,接口500……

你以为只是“发一下”,其实要穿越重重险境才能抵达。

结果就是:

- 消息发不出去 → 用户以为被无视;

- 或者重试太多 → 对方收到一堆重复“在吗?”;

- 最后用户体验崩了,客服工单爆了。

所以问题本质不是“快不快”,而是:

“宁可慢点,也不能丢;就算重发,也不能重复。”

这就是我们常说的可靠消息投递 ——一个看似简单的需求,却是高可用系统的分水岭。

4、解决方案:三层兜底,像保险一样层层防

光靠“发一次”肯定不行。

我们要学保险公司,给关键消息上三重保险:

  • 1)自己先复印一份存档 → 客户端本地存
  • 2)邮局签收后锁进保险柜,并异地备份 → 服务端落盘 + 副本
  • 3)如果没收到回执,隔段时间再寄,但对方只认一次 → 超时重试 + 幂等去重

每一层都不贵,合起来却能扛住99%的异常。下面看每层怎么落地。

5、第一层:客户端兜底 —— 消息先存本地,解决网络不稳定问题

记住一句话:只要没收到 ACK,就当没发成功。

所以第一步不是联网,而是先把消息塞进手机本地数据库(比如 SQLite)。

就像下面这样:

db.saveLocalMsg(msg); // 先落库,保命

boolean sendOk = network.send(msg);

if (!sendOk) {

    scheduleRetry(msg, 1000); // 发失败?排队重试

}

再加上客户端scheduleRetry  采用阶梯式重试策略:

  • 1)第1次失败 → 1秒后重试
  • 2)第2次失败 → 3秒后重试
  • 3)第3次失败 → 5秒后重试

避免雪崩式刷屏,既保障可靠性,又不压垮服务。只有等到服务端明确说“我收到了”,才把这条消息从本地删掉。

就像快递发货单:客户签收了,你才能撕票。

这样哪怕 App 崩溃、手机重启,下次打开照样继续发——用户体验无缝衔接。而如果不做这一步?一旦断网或崩溃,消息直接蒸发,用户永远不知道。

6、第二层:服务端兜底 —— 实现 服务端持久化的高可靠

客户端发来了,服务端能不能直接处理完就返回?绝对不行!

如果此时机器宕机,消息还在内存里没来得及持久化,那就真的丢了。

正确做法是两步走:

  • 1)收到消息立刻写入 RocketMQ(支持刷盘、集群同步);
  • 2)同步复制到至少3个副本节点,确保单点故障不丢数据。

伪代码如下:

rocketMQ.send(msg); // 必须落盘,断电也不怕

replicaService.syncTo3Replicas(msg); // 多副本容灾

response.sendAck(msg.getUniqueKey()); // 此时才能回 ACK

这一步的关键是:ACK 必须在落盘之后发!否则就是“虚假确认”,等于骗客户端“我收到了”,其实自己也没保住。

这一层扛住了服务端单机崩溃的风险,是整个链路的数据基石。

7、第三层:幂等性设计 —— 保障exact one

前面两层解决了“存得住”的问题,但这还不够。现实是:网络可能超时、包可能丢失、ACK 可能没传回来。

于是客户端必须重试。但重试带来新问题:

“我已经处理过了,再来一遍怎么办?”

解决办法是:用唯一键 + 幂等控制。

每个消息生成全局唯一的 key(如 sessionID:msgID),服务端通过 Redis 的原子操作判断是否已处理。

就像下面的代码这样:

String uniqueKey = msg.getUniqueKey();

if (redis.setNx(uniqueKey, "processed", 86400)) {

    processMsg(msg); // 第一次来,正常处理

} else {

    log.info("重复消息,忽略:{}", uniqueKey);

}

setNx 是关键:只有 key 不存在时才设置成功,保证多实例并发下也不会重复消费。

8、IM消息可靠性架构的核心流程总结

上面三层如何联动?一张图讲清楚全链路生命周期:

2-1

整条链路形成闭环:任何环节出问题,都有对应兜底机制接管。

9、本文小结

至此,《如何保障分布式IM聊天系统的消息有序性和可靠性》这期文章的上下两篇就完结了(上篇点此查看),上篇涉及到的分布式IM聊天系统架构中关于消息有序性问题,下篇则主要聚焦的是消息可靠性问题。

如果你是IM开发新人,想要系统地学习移动端IM开发的话,建议从我整理的这篇《新手入门一篇就够:从零开发移动端IM》开始,这样能保证IM开发知识能从网络到应用层、再从局部设计到整体架构,都有一个系统的学习脉络而不是在信息碎片中苦苦总结。

10、参考资料

[1] 什么是IM聊天系统的可靠性?

[2] 什么是IM聊天系统的消息时序一致性?

[3] 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

[4] 马蜂窝旅游网的IM系统架构演进之路

[5] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[6] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[8] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[9] 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践

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

[11] 基于实践:一套百万消息量小规模IM系统技术要点总结

[12] 一套分布式IM即时通讯系统的技术选型和架构设计

[13] 转转平台IM系统架构设计与实践(一):整体架构设计

[14] 移动端弱网优化专题(一):通俗易懂,理解移动网络的“弱”和“慢”

[15] 移动端弱网优化专题(二):史上最全移动弱网络优化方法总结

[16] Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

[17] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

[18] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

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

[20] 如何保证IM实时消息的“时序性”与“一致性”?

[21] 一个低成本确保IM消息时序的方法探讨

即时通讯技术学习:

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

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

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

posted @ 2026-02-02 15:42 Jack Jiang 阅读(42) | 评论 (0)编辑 收藏

本文引用了45岁老架构师尼恩的技术分享,有修订和重新排版。

1、引言

分布式IM聊天系统中,IM消息怎么做到不丢、不重、还按顺序到达?

这个问题,涉及到IM系统的两个核心:

1)消息不能丢(可靠性):比如用户点了发送,不能因为服务宕机或网络抖动,消息石沉大海。比如地铁隧道、电梯间,网络断了又连,消息不能卡住不动(要确保弱网也能用)。

2)顺序不能乱(有序性):比如“在吗?” 回成 “吗在?”,群聊时间线错乱,体验直接崩盘。

这二大痛点,是IM聊天系统架构的命门所在。

下面是一张IM消息从发出到接收的关键路径:

3

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。

本文是2篇文章中的第 1 篇:

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

3、传统技术方案的瓶颈,怎么破?

早期做消息有序,很多人第一反应是搞个“全局发号器”——所有消息排一队,挨个编号再发。

理想很丰满,现实很骨感:高并发下一拥而上抢号,发号器直接被打满;更致命的是,它一旦宕机,全链路雪崩。

这就像春运火车站只开一个售票窗——再快也撑不过三分钟。

所以,我们必须换思路:不搞大一统,而是分片独立发号,让每个“窗口”自给自足,互不干扰。

4、痛点拆解:为什么消息会乱?

我们先还原一个真实场景: 想象一下你和朋友聊天:

你说:“1 吃饭了吗?”

他回:“2 刚吃完。”

你又说:“3 吃啥呢?”

结果对方手机上显示成:

“3  吃啥呢?” → “1 吃饭了吗?” → “2 刚吃完。”

这不是 bug,是分布式系统的常态。

三条消息走不同服务节点、经不同网络路径,到达时间完全不可控,最终呈现顺序错乱。

会乱 问题本质是什么?一个要“串行等”,一个想“并发冲”,天然冲突。

这时候有人会说:那我加个全局排序服务不就行了?

可以,但代价太大——一个中心节点最多撑几万 QPS,面对百万群聊、亿级用户,还没上线就已过载。

所以,全局有序不是解,而是枷锁。我们要的不是“天下大同”,而是“各聊各的别乱就行”。

5、最终方案:分而治之 + 局部有序

真正的突破口在于:我们根本不需要全局有序,只需要“会话内有序”。

你和张三的聊天记录不能乱,但你和李四的聊天跟王五的完全无关——何必放一起排序?

这就引出了经典策略:分而治之 + 局部有序。

具体怎么做?两步走稳:

* 第一步 - 业务分区: 哈希分片,锁定归属

用 sessionId 做一致性哈希,确保同一个会话的所有消息始终路由到同一个处理节点。按“会话ID”做哈希,算出该消息该由哪个节点处理。同一会话 → 哈希值一样 → 路由到同一台机器 → 所有消息串行处理,天然避免跨节点乱序。

这样一来,单个会话内的消息在服务端就是串行处理的,天然不会乱。

* 第二步 - 局部序号:独立发号,局部递增

每个会话独立维护一个计数器,每来一条消息就+1,作为它的“官方序号”。每个会话,可以配一个独立计数器(比如 Redis 的 INCR),每来一条消息就+1,生成唯一 SEQ。客户端不管什么时候收到消息,只认这个序号,按序号从小到大排列展示。

这个 SEQ 就是这条消息的“官方身份证号”,客户端只认这个,不看接收时间。这就像电影院检票——你可以早到晚到,但座位按票号定。哪怕后排观众先进场,也不会坐到前排去。

PS:IM消息ID生成相关的文章可详细阅读以下资料:

  1. IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)
  2. IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)
  3. IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略
  4. IM消息ID技术专题(四):深度解密美团的分布式ID生成算法
  5. IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现
  6. IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)
  7. IM消息ID技术专题(七):深度解密vivo的自研分布式ID服务(鲁班)

6、实践落地(核心片段伪代码)

1)服务端分片路由逻辑:

来看关键实现:如何把消息精准投递给“对的人”。

String sessionId = msg.getSessionId();

//这里是伪代码,实际代码以mq 的负载均衡机制为准

int nodeIndex = Math.abs(sessionId.hashCode()) % clusterNodeCount;

 //这里写个伪代码,代表mq  主从复制

ClusterNode targetNode = clusterNodes.get(nodeIndex);

targetNode.sendMsg(msg);

核心就一句:基于会话 ID 哈希取模,固定路由。

从此,每个会话都有了自己的“专属服务通道”,不再受其他会话影响。

2)服务端序号分配逻辑:

接下来,给每条消息发“通行证”:

long msgSeq = redis.incr("msg_seq_" + sessionId);

msg.setSeq(msgSeq);

msg.setUniqueKey(sessionId + "_" + msgSeq);

这里用了 Redis 的 INCR,保证同一个会话下的 SEQ 绝对递增,且线程安全。同时用 sessionId_seq 作为唯一键,既能幂等去重,也能防止重试导致消息重复入库。

实战提示:

如果你的 Redis 是集群模式,记得确保同一个会话的 key 落在同一 slot,否则 INCR 可能跨节点失效。

3)客户端排序逻辑:

最后一步,客户端收尾:别急着渲染,先排好队。

//这里是伪代码, 先排序

List<Msg> sortedMsgs = msgList.stream()

    .sorted(Comparator.comparingLong(Msg::getSeq))

    .collect(Collectors.toList());

//这里是伪代码, 再渲染

renderMsgList(sortedMsgs);

无论消息以什么顺序到达,统统按 seq 升序排列后再上屏。哪怕第100条先到,第1条后到,也能正确归位。这也是为什么我们强调“客户端必须信任服务端 SEQ”——它是唯一真相源。

7、方案总结:放弃全局有序,换高可用与高性能

总结一下,这套方案的核心思想就一句话:

不要为“假需求”买单——我们不需要全局有序,只需要业务上有意义的有序。

你看微信、钉钉、飞书,哪一个是把全平台消息排成一条队列的?没有。

它们都选择了“会话级隔离 + 局部有序”的设计,这才是工业级系统的通用解法。

背后的分布式哲学也很清晰:

2

最终换来的是:

  • 1)高并发支持(水平扩展);
  • 2)高可用(无单点);
  • 3)强一致体验(用户无感知)。

这正是中高级开发者必须掌握的权衡思维:

不是技术做不到,而是要不要做。

有时候,“不做全局有序”,反而是最正确的选择。

8、 IM消息有序性架构的核心流程总结

最后,一张图串起全流程:

3
从发起到渲染,全程围绕“会话隔离”和“局部发号”展开。每一个环节都在为同一个目标服务:在分布式环境下,低成本实现用户可感知的“顺序正确”。

—— 下篇《如何保障分布式IM聊天系统的消息可靠性(即消息不丢)》稍后发布,敬请期待 ——

9、参考资料

[1] 什么是IM聊天系统的可靠性?

[2] 什么是IM聊天系统的消息时序一致性?

[3] 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

[4] 马蜂窝旅游网的IM系统架构演进之路

[5] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[6] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[8] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[9] 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践

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

[11] 基于实践:一套百万消息量小规模IM系统技术要点总结

[12] 一套分布式IM即时通讯系统的技术选型和架构设计

[13] 转转平台IM系统架构设计与实践(一):整体架构设计

[14] 移动端弱网优化专题(一):通俗易懂,理解移动网络的“弱”和“慢”

[15] 移动端弱网优化专题(二):史上最全移动弱网络优化方法总结

[16] Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

[17] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

[18] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

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

[20] 如何保证IM实时消息的“时序性”与“一致性”?

[21] 一个低成本确保IM消息时序的方法探讨

即时通讯技术学习:

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

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

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

posted @ 2026-01-19 21:29 Jack Jiang 阅读(58) | 评论 (0)编辑 收藏

本文由B站技术团队比奇堡、Xd、三木森分享,有修订和重新排版。

1、引言

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

cover-opti

B站技术团队的其它技术文章:

  1. B站千万级长连接实时消息系统的架构设计与实践
  2. B站实时视频直播技术实践和音视频知识入门
  3. B站基于微服务的API网关从0到1的演进之路

2、消息系统业务解读

1和2

按业务全域现状,在服务端角度分成客服系统、系统通知、互动通知和私信4个业务线,每个业务线内按现状标识了服务分层。私信内分为用户单聊、bToC的批量私信、群聊和应援团小助手四类,这四类细分私信没有技术解耦,单聊和批量私信比较接近系统天花板。

2

私信单聊发送到触达的pv转化和uv转化不足10%,有明显通过业务优化提升触达率的潜力。

3、消息系统中的私信业务

私信域内的几个概念解释:

1)会话列表:按聊天人排序的列表。即B站首页右上角信封一跳后看到的历史聊天人列表,以及点击未关注人等折叠会话看到的同属一类的聊天人列表。传达对方账号、最新私信和未读数的信息。点击一个会话后看到的是对聊历史,也称会话历史。

2)会话详情:描述和一个聊天人会话状态的原子概念,包括接收人uid、发送人uid、未读数、会话状态、会话排序位置等。

3)会话历史:按时间线对发送内容排序的列表。一份单聊会话历史既属于自己,也属于另一个和自己的聊天的人。群聊的会话历史属于该群,不属于某个成员。会话历史是收件箱和消息内容合并后的结果。

4)收件箱:将一次发送的时序位置映射到发送内容唯一id的kv存储,可以让服务端按时间序读取一批发送内容唯一id。

5)私信内容:一个包括发送内容唯一id、原始输入内容、消息状态的原子概念。批量私信把同一个发送内容唯一id写入每个收信人的收件箱里。

6)timeline模型:时间轴的抽象模型,模型包括消息体、已读位点、最大位点、生产者、消费者等基本模块,可以用于基于时间轴的数据同步、存储和索引。私信涉及timeline模型的包括会话列表和会话历史。

7)读扩散:pull模式。群聊每条私信只往群收件箱写一次,让成百上千的群成员在自己的设备都看到,是典型的读扩散。

8)写扩散:push模式。单聊每条私信既更新接收人会话也更新发送人会话,是轻微的写扩散,无系统压力。群聊有另一个不一样的特点,就是当群成员发送消息后,需要通过长链接通知其他群成员的在线设备,以及发送人其他的在线设备,这是一个写扩散的技术模型,但是这个写扩散是通知后即时销毁的,并且具有过期时间,所以仅临时占用资源,并不对存储造成压力,且能有较好的并发量。

私信核心概念关系表达:

3

4、消息系统问题1:会话慢查询

当会话缓存过期时,Mysql是唯一回源,Mysql能承载的瞬时QPS受当时应用总连接数和sql平均响应速度的影响,连接数打满时会给前端返回空会话列表。虽然可以增加POD数量、增大akso proxy连接数、优化sql和索引来作为短线方案,来提升瞬时请求Mysql容量,但是这种短线方案无法加快单次响应速度,mysql响应越来越慢的的问题依然在。另外增加POD数量也会降低发版速度。

4

会话Mysql使用用户uid%1000/100分库,用户uid%100分表,table总量是1000。

单表会话量在1kw-3.2kw。单个大up的会话积累了10W条以上,会话量最大的用户有0.2亿条会话。单个Up的会话会落到一张表中,每张表都有比较严重的数据倾斜。如果考虑增加分库分表的方案,sql查找条件依然需要用户uid,所以相当于倾斜数据要转移到新的单表,问题没有解决。另外,重新分库分表过程中新旧table增量同步和迁移业务读写流量的复杂度也很大,有比较大的业务风险。

Mysql的规格是48C 128G和32C 64G。由于会话数据量大,Mysql buffer_pool有限,数据比较容易从内存淘汰,然后mysql需要进行磁盘扫描并将需要的数据加载到内存进行运算,加之比较多的磁盘扫描数据,这时的响应一般在秒级别,接口会给前端返回超时错误,会话列表页空白。

为了适配业务发展,Mysql 会话表 已经添加了9个非聚集索引,如果通过增加索引使用业务需要,需要更大的Mysql资源,且解决不了冷数据慢查询的问题。增加更多索引也会让Mysql写入更慢。

5、消息系统问题2:私信内容单表空间和写性能接近天花板

每条私信内容都绑定私信自己的发号器生成的msgkey,即私信内容唯一id,该msgkey包含私信发送时的时间戳(消息ID生成可参阅读《微信的海量IM聊天消息序列号生成实践》)。读写私信内容Mysql之前先从msgkey解析出时间,用这个时间路由分库分表。

私信内容库按季度分库,分库内按月度分表,单表数据量数亿,数据量最大的用户日增私信351.9W条。按照曲率预测,25年全年数据量有近百亿,如果继续按照月度分表,分表规则不适应增长。

当前该Mysql最大写qps 790,特别活动时写qps峰值预计是20k,但是为了保障Mysql服务整体的可靠,单库写流量我们需要控制在3000qps以下,无法满足写入量峰值时的需要。

5

此外,消息内容表结构包含了群聊、单聊和应援团小助手全部的属性,增加业务使用难度。绝大部分私信内容是单聊的。

6、消息系统问题3:服务端代码耦合

B站的四类私信包括:

  • 1)单聊;
  • 2)群聊;
  • 3)B端批量私信;
  • 4)应援团小助手。

这些私信都需要实现发送和触达两条核心链路,四种私信核心链路的代码逻辑和存储耦合在一起,代码复杂度随着业务功能上线而不断增加,熵增需要得到控制。

从微服务这方面来说,实例和存储耦合会带来资源随机竞争,当一方流量上涨,可能给对方的业务性能带来不必要的影响,也会带来不必要的变更传导。

7、消息系统新架构的升级路径

基于对私信现状的论述,可以确定我们要优化的是一个数据密集型 >> 计算密集型,读多写少(首页未读数)、读少写多(会话)场景兼具的系统。

同时需要拥有热门C端产品的稳定性、扩展性和好的业务域解耦。针对读多写少和读少写多制定了针对的技术方案。

具体的实施情况请继续往下阅读。

8、新架构的整体设计

结合B站业务现状,我觉得比较合理的架构:

6

一个兼顾复杂列表查询架构和IM架构的消息域框架,整体分四层:

  • 1)接入层:即toC的BFF和服务端网关;
  • 2)业务层:按复杂查询设计系统,用于各种业务形态的支撑;
  • 3)平台层:按IM架构设计系统,目标是实时、有序的触达用户,平台层可扩展;
  • 4)触达层:对接长链和push。

9、新架构具体升级1:端上本地缓存降级

端上应该支持部分数据缓存,以确保极端情况下用户端可展示,可以是仅核心场景,比如支付小助手、官号通知,用户在任何情况下打开消息页都不应该白屏。

10、新架构具体升级2:BFF架构升级

BFF网关吸收上浮的业务逻辑,控制需求向核心领域传导。服务端基于业务领域的能力边界,抽象出单聊、群聊、系统通知、互动通知和消息设置共五个新服务,提升微服务健康度。

7

新服务剥离了历史包袱,也解决一些在老服务难解的功能case,优化了用户体验,比如消息页不同类型消息的功能一致性;重新设计会话缓存结构和更新机制,优化Mysql索引,优化Mysql查询语句,减少了一个量级的慢查询。

8

11、新架构具体升级3:服务端可用性升级

11.1 概述

服务端按四层拆分后,集中精力优化业务层和平台层。

业务层:按复杂查询设计系统,用于各种业务形态的支撑

  • 1)冷热分离:多级缓存 redis(核心数据有过期)+taishan(有限明细数据)+mysql(全部数据);
  • 2)读写分离:95%以上复杂查询可以迁移到从库读。

平台层:按IM架构设计系统,目标是实时、有序的触达用户,平台层可扩展

  • 1)Timeline模型:依赖雪花发号器,成熟方案;
  • 2)读写扩散:单聊-写扩散,群聊-读扩散。

11.2 单聊会话

1)缓存主动预热:

用户在首页获取未读数是一个业务域内可以捕捉的事件,通过异步消费这个事件通知服务端创建会话缓存,提高用户查看会话的缓存命中率。鉴于大部分人打开B站并不会进私信,此处可以仅大UP预热。大UP的uid集合可以在数平离线分析会话数据后写入泰山表,这个泰山表更新时效是T+1。

监控UP会话数量实时热点,触发突增阈值时,通过异步链路自动为热点用户主动预热会话列表缓存。

对预热成功率添加监控,并在数平离线任务失败或者预热失败时做出业务告警,及时排查原因,避免功能失效。

2)泰山和Mysql双持久化:

增加泰山存储用户有限会话明细,作为redis未命中后的第一回源选择,Mysql作为泰山之后的次选。基于用户翻页长度分析后确定泰山存储的有限会话的量级。

9

redis 存储24小时数据,taishan 存储 600条/用户(20页),预设到的极端情况才会回源mysql从库。

对于ZSET和KV两种数据结构,评估了各自读写性能的可靠性,符合业务预期。业务如果新增会话类型,可以跟本次新增泰山有限明细一样,基于会话类型的具体规则新增泰山Key。

10

3)泰山长尾优化:

查询redis未命中时会优先回源泰山,考虑到泰山99分位线在50ms以下,而且Mysql多从实例都能承受来自C端的读请求,所以采用比泰山报错后降级Mysql稍微激进的对冲回源策略。

在泰山出现“长尾”请求时,取得比较好的耗时优化效果。可以使用大仓提供的error group结合quit channel实现该回源策略,同时能避免协程泄漏。整个处理过程在业务响应和资源开销中维持中间的平衡,等待泰山的时间可以灵活调整。

11

泰山最初没有数据,可以在泰山未命中时进行被动加载,保证用户回访时能命中。

4)一致性保证:

虽然我们重构了新服务,但是老服务也需要保留,用来处理未接入BFF的移动端老版本和web端请求,这些前端在更新会话时(比如ACK)请求到了老服务,新服务需要通过订阅会话Mysql binlog异步更新本服务的redis和泰山。为了避免分区倾斜,订阅binlog的dts任务使用id分区,这样方便的是一条会话在topic的分区是固定的。

为了避免两次请求分别命中泰山和Mysql时给用户返回的数据不一样,需要解决三大问题:

  • a. 当出现分区rebalance需要避免重复消费;
  • b. 当Mysql一条会话记录在短时间内(秒级)多次更新,要保证binlog处理器不会逆时间序消费同一个会话的binlog,即跳过较早版本的binlog;
  • c. 保证泰山写入正确并且从Mysql低延迟同步。

这三个问题都要保证最终一致性,具体解决方案是用redis lua脚本实现compare and swap,lua脚本具有原生的原子性优势。dts每同步一条binlog都会携带毫秒级mtime,当binlog被采用时,mtime被记入redis10分钟,如果下一条binlog的mtime大于redis记录的mtime,这条binlog被采用,否则被丢弃。

这个过程可以考虑使用gtid代替mtime,但这个存在的问题是每个从实例单独维护自己的gtid,当特殊情况发生mysql主从切换,或者dts订阅的从节点发生变更,gtid在CAS计算中变得不再可靠,所以我们选择了使用mtime作为Mysql会话记录的版本。

通过消费路线高性能设计保证泰山异步更新的延迟在1秒以内,并在特殊情况延迟突破1s时有效告警。高性能消费路线中,每个库的binlog分片到50个partition,业务提供不低于50个消费pod,单pod配置100并发数,按照写泰山999分位线20ms计算,每秒可以消费 50*100*(1000/20)=250000 条,大约线上峰值8.3倍,考虑dts本身的max延迟在600~700毫秒,同步泰山和redis的延迟会在700毫秒至1秒以内,符合业务预期。

12

11.3 收件箱

BFF已经从业务层和平台层将单聊读收件箱独立出来,本次升级主要是从存储做增量解耦 ,存量单聊收件箱的读流量可以访问旧表。 单聊新收件箱存储采用redis+泰山的模式,redis提供热数据,泰山提供全部数据并采用RANDOM读模式,让主副本都能分担读流量。

13

 

11.4 私信内容

本次升级主要如下:

  • 1)单聊增量数据独立存储,按照单聊业务设计表结构,和群聊、应援团小助手彻底解耦。
  • 2)写Mysql升级为异步化操作,提高写性能天花板,这种异步写Mysql改造不会影响读消息内容的可用性和设计。
  • 3)单聊分库规则升级为月度分库,单库内分表为100张。 群聊、应援团小助手和历史单聊依然使用旧的分库分表规则读写Mysql。

业务需要对增量单聊私信路由分库分表时,先从msgkey先解析出时间戳,找到用时间戳对应的月份分库,然后用msgkey对100取余找到分表。这种方案能达到按时间纬度的冷热数据的分离,同时由于msgkey取余的结果具有随机性,平衡了每张表的读写流量。这样预计2025年单表数据量能从9亿下降到900万。

14
15

11.5 批量私信

日常通道:日常批量私信任务共用通道,共用配额。

高优通道:主要通过将链路上topic partition扩容、消费POD扩容、POD内消费通道数扩容、缓存扩容、akso proxy连接数扩容,把平均发送速度从3500 人/秒提高到30000人/秒。这个通道可以特殊时期开给特殊业务使用。

12、本文小结

我们逐步发现技术升级不是一蹴而就的,它是一个逐步优化的过程。

设计技术方案前设立合适和有一些挑战的目标,但这个目标要控制成本,做好可行性。

设计技术方案的时候,需要清楚现有架构与理想架构的差距和具体差异点,做多个方案选型,并确定一个,这个更多从技术团队考虑。

其次要保证功能在新老架构平稳过渡,保证业务的稳定性。后面持续关注新老架构的技术数据,持续优化,老架构要持续关注它的收敛替换。

IM系统是一个老生常谈的话题,也是融合众多有趣技术难点的地方,欢迎感兴趣的同行交流研讨。

13、参考资料

[1] 浅谈IM系统的架构设计

[2] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端

[3] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

[4] 一套原创分布式即时通讯(IM)系统理论架构方案

[5] 从零到卓越:京东客服即时通讯系统的技术架构演进历程

[6] 蘑菇街即时通讯/IM服务器开发之架构选择

[7] 微信技术总监谈架构:微信之道——大道至简(演讲全文)

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

[9] 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

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

[11] 从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

[12] 瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)

[13] 阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

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

[15] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[16] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[18] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[19] 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路

[20] 基于实践:一套百万消息量小规模IM系统技术要点总结

[21] 跟着源码学IM(十):基于Netty,搭建高性能IM集群(含技术思路+源码)

[22] 一套十万级TPS的IM综合消息系统的架构实践与思考

[23] 得物从0到1自研客服IM系统的技术实践之路

[24] 一套分布式IM即时通讯系统的技术选型和架构设计

[25] 微信团队分享:来看看微信十年前的IM消息收发架构,你做到了吗

[26] 转转平台IM系统架构设计与实践(一):整体架构设计

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

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

即时通讯技术学习:

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

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

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

posted @ 2026-01-06 17:34 Jack Jiang 阅读(76) | 评论 (0)编辑 收藏

     摘要: 本文由45岁老架构师尼恩分享,感谢作者,有修订和重新排版。1、引言你有没有想过,为什么 ChatGPT 的回答能逐字逐句地“流”出来?这一切的背后,都离不开一项关键技术——SSE(Server-Sent Events)! 本文从SSE(Server-Sent Events)技术的原理到示例代码,为你通俗易懂的讲解SSE技术的方方面面。2、A...  阅读全文

posted @ 2025-12-23 15:07 Jack Jiang 阅读(71) | 评论 (0)编辑 收藏

本文由腾讯技术团队颜勇分享,原题“腾讯新闻PUSH架构升级之路”,有修订和重新排版。

1、引言

68 万行代码精简到8.6 万;Golang 重写大部分 C++模块;解决过度微服务化问题…… 这是新闻 PUSH 架构团队取得的技术收益。

PUSH 是腾讯新闻精品资讯的重要分发途径,也是新闻 App 重要的促活手段。作为 PUSH 架构团队,我们一方面在积极支持好新闻护盘,同时也在对 PUSH 架构进行不断的升级与进化,以持续提升 PUSH 系统的稳定性与质量、研发效率,同时持续减少运营成本。

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

cover-opti

2、Push平台介绍

2.1 概述

PUSH 是腾讯新闻内容重要的分发渠道,新闻 PUSH 平台承担着将新闻资讯触达到新闻用户、满足用户及时获取精品资讯的需求。

1

总体上,新闻 PUSH 链路分为下面两部分。

2.2 PUSH触发

按触发方式的不同,新闻 PUSH 分为三类:

  • 1)人工 PUSH:运营在 push cms 系统指定要发送的文章、要触达的人群包,人工触发push发送;这类 PUSH 目前主要用于推送热点事件/热点资讯等;
  • 2)自动化 PUSH:周期性地给用户计算他可能感兴趣的内容,这类推送由后台自动触发;
  • 3)功能性 PUSH:由业务系统触发,主要是为了实现一些业务功能通知,比如评论通知、关注通知等。

2.3 PUSH下发

对于所有 PUSH 触发 的PUSH 进行调度(包括避让、打散和频控等)和触达(通过自有通道或厂商通道推送给用户)。

新闻业务对新闻 PUSH 平台最重要的要求是:

1)要保证精品咨讯触达的及时性:

新闻 PUSH 最重要的是要体现“新”,因为腾讯新闻用户有及时获取热点/突发资讯的诉求,用户经常有这样的体感,有热点突发事件时,所有 App 都会尝试第一时间向用户发起推送,用户大概率会点击收到的第一个推送。在了解了相关热点事件后,对于后续其它 App 的推送,对用户而言就没信息量了,大概率会被忽略,甚至可能会被用户视为一种打扰,影响用户体验。从我们实验数据来看,当P USH 下发延迟降低 50%,PUSH 点击量会提升 10%。

所以新闻 PUSH 一直以来的目标是:热点资讯需要第一时间触达给用户,要做到“全网首推”。

2)要保证推送的用户体验和较好的拉起效率:

PUSH 是新闻重要的促活手段,需要有较好的促活效率,这要求保证用户较好的推送体验,因为用户如果感觉推送体验不好,用脚投票,把 App 的 PUSH 系统开关给关了,这对 PUSH 而言就基本上就永远丧失了给这个用户推送的机会了。这就要求要尽量保证在合适的时间点给推送用户感兴趣的内容,推送要有合理的频次,相邻 PUSH 之间要有合理的时间间隔,推送内容要做合适的打散。

其实这两个要求其实在一定层面上是有冲突的:

  • a.如果要保证推送的及时性,就要求尽量减少计算,拿到消息消息后无脑推到消息通道,这个肯定最快;
  • b.如果要保证良好的推送用户体验,就需要做很多的判断、考量和计算,这些考虑越多就需要做更多的计算和 io 操作,会影响推送的及时性;最近几年,业务成本的考虑也是 PUSH 关注的重点,需要削减使用的机器和资源,就要求用更少的机器如何发得更快更好。

总结而言,之前新闻 PUSH 业务的突出问题主要有两个方面,请继续往下阅读。

3、Push平台问题1:推送速度慢

我们团队从 2022 年年中开始接手新闻 PUSH 平台。交接工作刚启动,就遇到了一次 S 级热点事件——一个国际级突发新闻。那天晚上,全网用户都在密切关注它的最新进展。

这个事件有两个特点:热度极高、且并非完全突发——早在一个月前就已经有明确预告,因此运营部门提前布置了应急预案。我们刚接手系统时,对整个下发链路还不够熟悉,只能凭直觉扩容机器,希望能抗住峰值。结果现实很快给了我们一记当头棒喝。

当晚,很多内部同事都装着多个新闻 App,一眼能看到谁家的推送更快。那晚我们的延迟问题非常明显,甚至有用户在热点过去一个多小时后才收到通知。事后有专门的评测团队做了分析,指出“PUSH 下发耗时过长,高活用户 P90 均值达 20 分钟”,报告还发到了高层群里——对我们来说,那无疑是一次刻骨铭心的教训。

4、Push平台问题2:开发效率和问题排查效率低

之前 PUSH 链路特别长,新闻 PUSH 内部有 30+ 个模块,同时还依赖其它两个跨业务团队。经常一个需求开发要改多个模块,要团队几个人一起开发,约定交互协议,开发后再联调测试,在多个模块起联合实验;然后还得给中台提需求,然后匹配中台的排期后,才能完成需求上线;这一系列操作就拉长了 push 需求的leadtime。

线上有 case 时,问题排查也需要串联多个模块,关联多个模块数据,甚至需要跨部门拉上其它这边来一起来排查,排查效率非常低。push case 非常多,比如用户为什么收到了/没收到某条 push 之类的典型 case,之前需要关联链路20来个模块的日志,还要联合中台一起排查,每次 case 排查时间都在天级;之前在case 排查上,每天都耗费我们大量的人力。

既要持续提升 PUSH 触达的及时性、又要持续提升推送的用户体验和拉活效率,还要持续降低运营成本,客观而言,在技术上是一个较大的挑战。本文主要详述,我们如何通过技术架构升级来支撑这个既要&又要&还要的目标。

5、老Push架构的问题梳理

5.1 模块链路过长,内耗过多

一条快速PUSH,从推送内容过审后,到最终发出去,最长要经过18个模块,另外还需要经过中台多个模块。一条待推送的数据最多要经历 17 次内部 rpc 转发,多个模块之间腾挪流转,各种网络 rpc,各种内耗,肯定发得慢。

一个最典型的例子:原架构有个模块叫scheduler,它主要负责决定一个push该不该发,直观上感觉它里面应该囊括了各种过滤策略,但是原架构做成了多个微服务。scheduler 模块里本身有一些过滤逻辑,另外有一个叫做 filter 的模块,专门负责品牌、开关等硬规则过滤;另外有一个叫做 policy 的模块,专门负责配额等软规则过滤;所有过滤规则都通过后,进入一个叫做 channer 模块,就决定下这次推送走哪个通道;然后又走到一个叫 worker 的模块里,而它只做对接下游中台的协议适配。

总体上看,原链路就是过度微服务化了:

  • 1)模块多会导致数据流转的低效,模块间网络 rpc 会浪费处理耗时;
  • 2)其次会影响迭代效率,模块数不是越多越好,因为经常一个需求需要改多个模块,做多次上线;
  • 3)同时模块过多也对联调&测试效率,影响线上 case 排查效率。

这就违反了“模块内高内聚,模块间低耦合”的架构设计原则,进而会影响业务迭代效率。

5.2 依赖服务有瓶颈

上文提到的 S 级热点事件时,我们将下发服务机器扩了一倍,但是下发速度并没有提升,说明瓶颈不在下发服务本身下,而是在依赖服务上;通过链路debug,我们定位到了链路瓶颈:号码包拉取。

在发送人工 push,运营会指定受众人群包(几百万到几亿不等),这时候需要分页拉取该号码包数据进行处理。

之前老架构使用了底层平台的人群包服务,新闻所有 push 人群包都上传到了该人群包服务,当发送指定人群包,需要请求平台侧接口分页拉取人群包数据,当时因为平台侧人群包功能实现比较复杂,能支持一些比较高级的能力,因此这个分页接口耗时比较长。但其实我们只用到了最简单的数据分页的功能,完全可以采用更简单的实现方案,以减少接口耗时。

5.3 链路稳定性不好

5.3.1)容错能力差:

之前链路基本无容错能力,发生了过一次因上游未按约定协议跟我们请求交互,导致我们服务挂了半天,是一次典型的 P0 级事故。

5.3.2)缺少节点自动故障转移:

scheduler 负责 push 调度,原架构为了提升处理效率,scheduler 里做了本地缓存;为了避免缓存失效,起了一个服务 dispatch 消费触发侧生产的待推送的消息,然后按照用户设备号一致性哈希来 sharding,通过 rpc 请求对应的 scheduler,scheduler接受到请求后,塞入到它本地的内存队列里,如果队列满了就直接丢弃。

它原来存在有这些问题:dispatch无脑往下游转发,sharding规则非常僵硬,一个用户的push一定要打到某个节点,未做故障转移;当某节点异常满载时,dispatch还是会往这个节点打,导致丢消息或者是 push发送得慢。而且当节点满载时,有限的cpu还需要耗费在rpc解包、无法插入内存队列而丢弃之类的无用消耗上。

5.4 链路处理无优先级区分

运营人工发的 PUSH 和自动化 PUSH 都使用同一个下发链路,热点突发事件资讯多由运营人工发送,而自动化 PUSH 多发一些用户可能感兴趣的内容,其实它对于推送速度并没那么敏感;当有人工推送的热点突发内容时,自动化 PUSH 会和它一起争抢有限的链路资源。

另外,在链路总吞吐量一定的情况下,其实处理顺序可以调整,让链路资源有限保证人工推送的热点突发内容的发送;

5.5 技术栈不统一

之前 push 下发链路有 C++/Go 两种技术栈,技术栈不统一不利于代码复用,影响需求迭代效率。push下发链路本质上是一个高 io 型的流程,其实可以完全可以统一到 Golang 技术栈。

5.6 链路测试效率低

push 链路业务逻辑比较多,在日常密集业务需求迭代中,新功能我们可以在线上通过构造对应的功能 case 来进行冒烟测试,但是比较难评估是否影响了线上已有的业务逻辑。

之前缺乏有效的回归测试手段,由于担心影响线上业务指标,为了验证是否影响线上已有业务逻辑,我们大的修改都会开比较长的小流量实验验证,比如我们在做调度架构升级时,开了一个近两个月的小流量实验,测试效率比较低也会导致需求迭代效率比较低。

6、新Push架构优化1:消息通道自建

之前新闻 PUSH 依赖于平台侧的消息通道,业务侧主要负责 PUSH 调度,即业务侧决定触发和过滤,平台侧负责 PUSH 触达给用户终端。

由于 PUSH 是新闻增长护盘的重点方向,有较频繁的业务迭代,对底层消息通道我们有较多的业务需求,在业务迭代过程中我们发现平台侧需求 leadtime 比较长,无法满足业务侧迭代效率的要求;在经平台侧这边商量且同意后,我们完成新闻push消息通道的自研,直接对接厂商推送并搭建了长链接通道,实现了 push 全链路在业务侧的全闭环。

我们在自建 push 消息通道时,对原来的架构做了重写:

1)精简链路,模块整合,减少系统复杂度:去掉我们不关心的无用功能,将原链路15个模块,代码 68 万行整合为了6个模块,代码共8.6万行;通过代码精简能减少系统复杂度,有助于提升业务迭代效率;同时能避免模块之间的rpc通信开销,提升链路处理效率。

2)客户端/服务端交互接口整合,提升数据通信成功率:以前 PUSH 注册依赖于注册&绑定&上报三个接口请求,任何一次请求出错,push 注册就会失败;我们在新流程里将注册&绑定&上报需要的所有数据,都一起传给新接口,由服务端在一个接口里实现注册、绑定和上报;将注册成功率从90%提升到了99.9%。

3)与新闻技术技术架构保持统一:将原架构发现/rpc技术栈的基础组件升级为腾讯新闻自用的基础组件,尽量使用我们熟练使用的技术栈,以提升业务开发&运维效率。

4)优化了原来链路一些不合理的地方:对原来链路的限流机制、通道选择策略做了优化,增加了必要的功能,比如小流量实验环境的支持。

7、新Push架构优化2:统一技术栈

之前 push 链路有 C++/Golang 两种技术栈,除了 push 推荐服务外, 其它 C++链路模块全部使用 Golang 模块进行了重写,以提升业务迭代效率和链路稳定性。

8、新Push架构优化3:链路整合升级,提升效率

一个架构如果如果过度微服务化了,会带来各种问题:

  • 1)模块间耦合严重,影响研发效率:本来是一个模块应该完成的工作,硬拆成了2个模块,有改动需要都需要改两个模块,需要模块间联调测试,影响需求迭代效率。
  • 2)架构效率低:拆成微服务后,函数本地调用变成了RPC网络调用,需要增加大量的拆包、解包的操作,资源白白浪费在这些无用的内耗上了。

对于频繁迭代的地方,单独抽成单独的微服务是有助于提升迭代效率的;但是我们review历史push需求,都比较分散,没有集中到一个特定的地方,我们按照“一个需求尽量只用改一个模块”的原则,对原来的push链路的所有模块进行了整合升级。

具体的升级内容是:

  • a. 触发侧合并为了1个模块:将原来触发侧的5个模块合并为1个模块;
  • b. 调度侧合并为了1个模块:将原来调度侧的5个模块合并为了1个模块;
  • c. 将消息通道侧模块做了整合:如上所述,我们将push消息通道原来15个模块合并为了5个。

经过链路整合后:以前一个 PUSH 消息最多要经过 18 个模块,17次内部链路rpc转发;升级后,只用经过 3 个模块,只用经过 2 次 rpc 转发;这样就显著提升了链路效率;而且模块减少后,业务需要迭代无需开发多个模块,避免模块之间联调和测试,提升了业务迭代效率;同时,线上 case 排查时,无需做多模块的日志 join,提升了 case 排查效率。

9、新Push架构优化4:自建号码包服务,提升号码包获取速度

如上文所述:之前号码包的拉取慢是系统的主要瓶颈所在,而在我们这个场景比较简单,因此我们考虑自建号码包服务,针对于我们自己的需求来定制开发,以提升服务性能。我们的需求只有一个,就是对离线包进行分页,并提供服务接口返回指定页的数据。

1)画像中台圈选兴趣包,并按页切成若干个小文件,每个兴趣包一个文件夹,并上传到cos,兴趣包里带着数据版本号;

2)构建包管理服务,提供获取指定兴趣包指定页数据的能力;包管理服务定期从cos上check是否有更新的数据(比较本地数据版本和cos最新的数据版本),如果有,则拉取最新的数据更新本地数据;当接收到拉取指定包指定页数据的请求后,则定位到对应文件夹读取对应页文件数据并返回;

3)集群有个数据一致性哨兵,定期检查集群节点的数据版本,当发现集群数据版本不一致时,给集群所有节点发信号,强制让每个节点同步cos上的最新数据,让集群所有节点数据跟最新数据保持一致。

2

10、新Push架构优化5:在线过滤改成离线预处理,避免在线处理耗时

运营在发PUSH时会选择受众人群包,同时会指定系统、品牌等筛选项,之前的处理流程是先把人群包一股脑发到链路里,然后在下发链路里根据用户画像数据,对数据进行实时过滤。在线过滤增加了链路下发的耗时。

其实系统&品牌过滤完全可以前置到离线侧,我们将号码包按品牌和系统维度进行了拆分,比如“社会”包按 android/ios、huawei/oppo/vivo/honor/xiaomi,拆成了13个包,当运营选择指定的筛选项时,直接拉取对应的号码包,这样就避免了在线过滤的耗时,减少了下发的延时。

11、新Push架构优化6:将单IO操作自动聚合成批量操作

push下发链路有大量io操作,比如获取用户维度的多路数据(比如用户系统、品牌、下发&曝光&点击历史等),获取文章维度的多路数据(文章正排数据等)。链路其实主要耗时还是在io部分,如果能提升io吞吐量,就能提升PUSH链路的吞吐量,减少下发延时;io操作批处理肯定能提升吞吐量。

但是在具体业务流程中,不同push类型、不用品牌用户,处理逻辑会有不同,因为每个push的处理流程可能都不一样,无法直接批处理。所以之前调度主链路流程是从队列里按单个消费进行处理的。

为了提升链路吞吐量,我们对每一类io操作做了一个类,对外暴露一个单个io请求接口,外部调用该接口后,将请求压入一个异步队列,同时开始等待结果的返回;这样该类io请求都会在该异步队列里进行了汇聚。

下层会开若干个处理协程,批量从异步队列消费出若干请求任务,拼成批量的io请求,然后拿到批量io结果,按序向上层返回io结果;

这样对上层而言,看到的还是单个的同步io接口,上层业务逻辑开发流程无需做改造,底层其实已经自动做了io的批量聚合,显著提升了链路吞吐量。

12、新Push架构优化7:优先推送热点突发内容,优先保证高价值用户及时性体验

在链路吞吐量一定的情况下,一个推送任务小到几百万,大到一两亿的发送量,都需要处理时间。这时候先处理比后处理的时延要少。

其实可以考虑对链路发送进行调度:

  • 1)链路优先保障热点突发PUSH的发送,我们建立了任务优先级队列,当有热点突发PUSH在发送时,其它PUSH延迟发送;
  • 2)同一个PUSH任务,对用户推送顺序也做了排序:活跃度高、历史push点击率高、预估商业化价值高、对push时延敏感的用户优先发送。

通过优先级调度,最大程度保障了热点突发内容和高价值用户的推送及时性的体感。

13、新Push架构优化8:增加自动故障恢复能力

为了提升链路吞吐量,调度节点进程通过 LRU cache 缓存了大量数据,所以在推送消息处理的 sharding 方式上采用了按设备号一致性哈希。

很多时候某个节点异常时,会出现慢而不死的情况:处理能力陡降,但是节点存活正常。北极星未能把它摘掉,相当一部分设备会打到该节点,即使该节点已经满载了,之前架构为了避免缓存失效而导致处理耗时增加,还是会一致性哈希将流量打往该节点,导致这部分用户处理耗时异常增加,甚至发送失败。

新架构对于推送任务sharding做了优化:在一致性哈希的基础上,每个节点计算出4个固定的backup;当某节点的失败率或处理耗时超过一定阈值时,将该节点的流量均匀低分给他的backup。通过这种方式就支持单节点异常时的自动故障恢复。

14、新Push架构优化9:构建push链路自动化测试能力

构建了接口自动化回归测试流程:

  • 1)case覆盖push链路的核心逻辑;
  • 2)合并master时自动触发回归测试流程的执行。

构建了自动化diff测试流程:

diff流程大体思路都类似,通过录制线上流量的真实请求和返回结果,在测试环境进行回放,观察同一请求下,返回结果是否会有差别;如果无差别,说明测试环境跟线上一样,上线不会引起线上数据异常;如果有差别,就需要分析这些差别是否是符合预期的。

diff测试基本能回归到线上所有业务逻辑分支,能弥补回归测试覆盖度有限的问题。

主要挑战:

push依赖的数据变化比较快,导致在同一时间,同一请求的返回结果会不同;比如push为了避免重复下发同一篇文章,会依赖于下发历史数据,线上录制了刚下发的某篇文章,在测试环境去回放肯定就不能下发了,因为线上刚把这篇文章写入到下发历史里,导致回放请求时返回结果是不能下发了,这样自然就产生了diff。

解决方案:

在流量录制时,除了录制请求之外,同时录制各个依赖数据,在回放时,依赖数据以依赖数据为准,通过这种方案就避免了依赖数据易变而引入diff的问题。

15、架构升级后的系统表现

1)push运营成本显著降低:通过持续的 push 架构优化,新闻 push 总运营成本下降70%;

2)PUSH链路性能(吞吐量)显著提升:通过持续的 push 架构优化,显著提升了 push 链路的性能,push推送量(出口)峰值吞吐量提升了3.5倍;

3)热点突发(全国/快速)PUSH全链路耗时下降明显:

  • a. 热点突发(全国/快速)PUSH内部链路耗时P90下降了90%;
  • b. 内部链路耗时指的是从push审核通过到推送给厂商的时间,即我们内部链路总的耗时时长;
  • c. 热点突发(全国/快速)PUSH全链路耗时(包括内部链路耗时和厂商链路耗时)下降了90%
  • d. 全链路耗时指的是从push审核通过到用户收到PUSH时间,即包括内部链路和厂商链路总的耗时时长.

我们完成一些架构升级后,还是评测团队对了评测,腾讯新闻的PUSH已经领先于竞品1~4分钟了。

4)提升了PUSH点击效果:

push推送速度提升后,push点击数据也能看到明显受益,热点突发PUSH点击pv提升了10%,push大盘点击UV也能看到显著的正向收益;

线上收不到PUSH的用户客诉也减少到25年H1 0 例,提升了用户产品体验。

5)稳定性良好:push链路主要重构完成后,PUSH链路稳定性&质量明显提升,2025.02以后 0 故障。

16、参考资料

[1] 极光推送系统大规模高并发架构的技术实践分享

[2] 魅族2500万长连接的实时消息推送架构的技术实践分享

[3] 专访魅族架构师:海量长连接的实时消息推送系统的心得体会

[4] 一个基于长连接的安全可扩展的订阅/推送服务实现思路

[5] 实践分享:如何构建一套高可用的移动端消息推送系统?

[6] Go语言构建千万级在线的高并发消息推送系统实践(来自360公司)

[7] 腾讯信鸽技术分享:百亿级实时消息推送的实战经验

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

[9] 京东京麦商家开放平台的消息推送架构演进之路

[10] 技术干货:从零开始,教你设计一个百万级的消息推送系统

[11] 长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践

[12] 喜马拉雅亿级用户量的离线消息推送系统架构设计实践

[13] 直播系统聊天技术(四):百度直播的海量用户实时消息系统架构演进实践

[14] 消息推送技术干货:美团实时消息推送服务的技术演进之路

[15] 揭秘vivo百亿级厂商消息推送平台的高可用技术实践

[16] 得物从零构建亿级消息推送系统的送达稳定性监控体系技术实践

[17] B站千万级长连接实时消息系统的架构设计与实践

[18] 转转千万级用户量消息推送系统的架构演进之路

[19] 企业级实时消息推送系统的架构设计,一文即懂!

技术交流:

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

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

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

posted @ 2025-12-08 19:12 Jack Jiang 阅读(60) | 评论 (0)编辑 收藏

本文由自字节跳动技术肖新蔚、赵彦奇分享,有修订和重新排版。

1、引言

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

cover-opti

技术交流:

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

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

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

2、系列文章

移动端弱网优化专题(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端弱网优化专题(二):史上最全移动弱网络优化方法总结

移动端弱网优化专题(三):现代移动端网络短连接的优化手段总结

移动端弱网优化专题(四):百度APP网络深度优化实践(DNS优化篇)

移动端弱网优化专题(五):百度APP网络深度优化实践(网络连接优化篇)

移动端弱网优化专题(六):百度APP网络深度优化实践(移动弱网优化篇)

移动端弱网优化专题(七):爱奇艺APP网络优化实践(网络请求成功率优化篇)

移动端弱网优化专题(八):美团点评的网络优化实践(大幅提升连接成功率、速度等)

移动端弱网优化专题(九):淘宝移动端统一网络库的架构演进和弱网优化实践

移动端弱网优化专题(十):爱奇艺APP跨国弱网通信的优化实践

移动端弱网优化专题(十一):美图APP的移动端DNS优化实践

移动端弱网优化专题(十二):得物自研移动端弱网诊断工具的技术实践

移动端弱网优化专题(十三):得物移动端常见白屏问题优化(网络优化篇)

移动端弱网优化专题(十四):携程APP移动网络优化实践(弱网识别篇)

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

3、技术背景

在字节跳动的业务生态中,HTTPDNS 承担着为抖音、今日头条、西瓜视频等核心应用提供域名解析服务的重任。但目前我们所采用的业界主流缓存机制(火山Cache1.0),却存在着调度不准的问题。

这些问题主要是:

  • 1)业界主流缓存机制的问题;
  • 2)缓存粒度:城市-运营商;
  • 3)致命缺陷:当自身IP库与权威DNS服务器不同,易发生调度不准,可能影响用户体验。

4、主流HttpDNS调度修正机制的局限性

针对 HTTPDNS 调度不准风险,业界主流处置流程采用 “发现-定位-修复” 三步闭环机制.

具体如下:

  • 1)发现:通过监控告警、业务异常反馈等方式,识别存在调度偏差的解析场景;
  • 2)定位:结合访问日志、链路追踪数据等,定位调度不准的具体域名、源IP段和目标 IP 段;
  • 3)修复:通过技术手段修正解析结果。

针对上述第 3)点,核心修复方式包含以下两类(均存在显著局限性):

  • 1)地址库升级:基于外部供应商数据聚合构建的 IP 地址库,即使实时更新,仍难与外部 CDN 厂商的映射保持一致;
  • 2)临时劫持:手动配置解析劫持规则修正解析结果,不仅操作流程繁琐、耗时长,且需人工维护大量静态配置;若规则未得到及时维护,易引发解析结果异常。 
1

5、主流厂商的HttpDNS缓存粒度技术方案

缓存粒度设计直接影响 DNS 解析精准度,主流厂商的方案存在明显差异:

2

6、HttpDNS的缓存键精细化重构

我们综合考量调度精准度、工程复杂度以及成本,决定将缓存粒度由“城市+运营商”细化为“网段”。

6.1 传统方案(国内某厂商/火山Cache1.0)

3

  • 1)缓存粒度:城市+运营商;
  • 2)污染范围:整个城市运营商;
  • 3)调度准确性:低。

6.2 Cache2.0方案

4
  • 1)缓存粒度:网段;
  • 2)污染范围:单个网段;
  • 3)调度准确性:高。

6.3 网段自适应划分算法

背景:外部 CDN 厂商的调度结果会随网络拓扑和调度策略持续变化,而静态网段库划分方式固定,难以实时跟踪调度结果变化。

为解决这一问题,网段库动态划分算法通过“数据输入—一致性校验—网段调整—结果输出”的闭环流程,实现了网段库的自适应动态划分。

具体流程如下。

1)数据输入:

* 收集客户端IP—CDN IP映射数据:

  • a)数据来源:主动拨测结果;HTTPDNS 递归节点日志;
  • b)数据范围:主流CDN厂商的解析结果。

* 网段归属判断:

  • a)若相邻客户端IP的CDN IP 归属同一运营商,则该组CIP可合并为连续网段;
  • b)将合并后的连续网段输出,作为探测网段数据集。

2)一致性校验:

  • a)将探测网段数据集与存量CIDRDB网段库进行逐网段对比,检查 “映射一致性”;
  • b)若存在映射不一致,则触发网段调整流程。

3)网段调整:

  • a)合并:探测数据集的网段比现有库粗,合并为大网段;
  • b)拆分:探测数据集的网段比现有库细,拆分为小网段。

4)结果输出:

  • a)生成优化后的新CIDRDB网段库;
  • b)替换存量网段库,实现动态更新。

5)持续迭代:

  • a)重复上述流程,实现网段库的自适应动态划分。
5

7、HttpDNS的缓存策略优化

为解决缓存粒度细化可能导致的命中率下降问题,Cache2.0 引入了四重优化策略,最终实现了如下收益:

缓存命中率提高了15%,缓存量、CPU 使用和出网流量降低了约70%。

1)两级一致性哈希分流:

火山 HTTPDNS 的流量转发以一致性哈希思想为核心,将用户请求链路(用户→LB→缓存层→递归层)拆分为两级哈希调度:

  • a)一级调度(LB→缓存层):以“源 IP + 域名”为哈希键。使用LB的一致性哈希策略,将同一用户对同一域名的请求统一路由至固定的 HTTPDNS 节点,避免传统轮询导致的请求分散;
  • b)二级调度(缓存层→递归层):以“域名 + 网段” 为哈希键。以 “域名 + 客户端网段” 作为哈希键,与缓存粒度完全对齐,确保某一“域名 + 网段”对应的查询请求均定向到唯一的递归层节点。

两级哈希协同调度,解决了缓存的碎片化问题,同时单一节点故障影响范围极小。

6

 


 

2)缓存分级管理:

在 HTTPDNS 场景中,不同域名对解析精度的需求不同。高优先级域名(如API 调用、直播 / 点播流媒体分发)对解析精准性要求高,跨网可能导致访问延迟增加;而低精度需求域名(如302域名)采用过细缓存会浪费存储资源,频繁回源也会增加权威 DNS 压力。

为实现缓存资源的精细化分配,火山 HTTPDNS 将缓存体系划分为“网段缓存、城市 - 运营商缓存、全局缓存” 三级,各级缓存适配不同应用场景。

具体是:

1)网段缓存:作为最高精度层级,聚焦高优先级业务场景 :一方面适配高优域名(如抖音 API 调用、图片分发、点播 / 直播流媒体传输等对精准性敏感的域名),另一方面服务重点集群(如 ToB 企业 HTTPDNS 服务、ToB 专属公共 DNS 服务),通过网段级细粒度缓存确保解析结果与用户实际网络链路高度匹配,降低访问延迟;

2)城市 - 运营商缓存:定位中等精度层级,适配普通域名场景:针对调度精准度要求较低的域名,以 “城市 + 运营商” 为缓存单元,平衡缓存命中率与存储开销;

3)全局缓存:作为基础精度层级,专门适配非智能解析域名:针对不支持 CDN 动态调度、解析结果无地域 / 运营商差异的域名(如静态官网、通用工具类服务域名),采用全局统一缓存策略,所有用户查询共享同一缓存结果,最大化提升缓存命中率,降低回源请求压力。

7

3)缓存更新分级策略:

在 HTTPDNS 系统中,统一的主动刷新策略虽然能保证缓存命中率,但存在明显问题:对不需要精细调度的域名浪费了存储资源,增加了下游压力。

基于以上问题,火山 HTTPDNS引入 “主动刷新 + 被动刷新”分级策略,以域名优先级和业务需求为依据,将缓存更新机制分为两类。

具体是:

  • a)后台线程主动刷新机制:针对高优域名(白名单),保留后台线程主动刷新,确保缓存持续有效、用户请求直接命中最新数据;
  • b)用户请求被动刷新机制:针对普通域名或非智能解析域名,由请求触发缓存更新,按需刷新,无需常驻后台刷新线程,降低资源消耗。

通过这种分级更新策略,高优先级域名仍能保证低延迟和高命中率,同时普通域名的刷新开销显著降低。

4)缓存预取机制:

依托 “缓存空间局部性原理”,火山 HTTPDNS 设计了缓存预取机制。当某条缓存请求(如 A 网段域名解析)触发更新时,系统不仅刷新目标网段缓存,还会同步更新与其具有 “亲缘关系” 的网段缓存(“亲缘关系”指地理相邻、同运营商节点覆盖的网段)。这种 “单次请求触发批量预取” 的设计能够提前将关联网段缓存置于准备状态,提升后续请求的命中率。

以抖音直播域名的实际访问场景为例,预取机制的运作过程如下:

  • a)本网段更新:当用户 A(IP 归属北京联通 10.0.1.0/24 网段)发起直播域名解析请求时,系统首先刷新其所属的 10.0.1.0/24 网段缓存;
  • b)预取更新:系统同时刷新与 10.0.1.0/24 网段具有亲缘关系的网段缓存,例如北京联通下的相邻网段(10.0.2.0/24、10.0.3.0/24),确保这些网段缓存也处于准备状态。

随后,当用户 B(10.0.2.0/24)或用户 C(10.0.10.0/24)发起相同直播域名的解析请求时,由于对应网段缓存已提前预取,无需等待回源即可直接命中缓存,显著降低访问延迟。

8、HttpDNS优化后的实际效果

8

服务端调度精准度提高:借助网段级缓存,用户获取的 IP 地址更加精准。按服务端日志数据口径,调度不准比例从万分之六下降至万分之二,降幅 60%,有效缓解了传统粗粒度缓存导致的“城市级缓存污染”问题。

客户端性能优化:

  • 1)成功率:核心 feed 接口,在弱网+非连接复用场景下提升 1.15%;
  • 2)耗时:非连接复用场景耗时减少14ms。
9

用户体验提升:

  • 1)性能指标:首刷及启动耗时下降;
  • 2)用户指标:用户行为指标(send 与 click)正向,用户活跃度提升。

本方案通过服务端精准调度 → 客户端性能优化 → 用户体验提升,实现了全链路效能提升。

9、持续演进方向——共享缓存

目前,各机房的负载均衡策略与缓存策略未能完全对齐(部分采用随机转发,部分虽然使用一致性哈希但粒度不一致),导致同一数据在多个实例中被重复缓存,资源利用率偏低,缓存命中率也有待提升。

未来,我们计划构建一个分层共享的高可用缓存体系:

1)在同一机房内,实例通过一致性哈希协同分工,每台实例既是分片缓存,也能代理转发请求,从而减少重复存储并提升命中率;

2)在跨机房层面,按区域部署二级缓存节点,作为容量更大、延迟更低的共享中心,承接一级未命中的请求,降低跨区域访问和上游压力。与此同时,引入热点数据副本、请求合并和故障转移等机制,保证高并发和异常情况下的稳定性与可用性。

通过这一演进,整体架构将逐步升级为层次化、分布式且具备高可用能力的缓存网络,为业务的持续扩展提供坚实支撑。

10、参考资料

[1] TCP/IP详解 卷1:协议 - 第14章 DNS:域名系统

[2] 网络编程懒人入门(七):深入浅出,全面理解HTTP协议

[3] 网络编程懒人入门(十二):快速读懂Http/3协议,一篇就够!

[4] 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

[5] 脑残式网络编程入门(三):HTTP协议必知必会的一些知识

[6] 全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等

[7] 通俗易懂,理解移动网络的“弱”和“慢”

[8] 现代移动端网络短连接的优化手段总结

[9] 百度APP网络深度优化实践(DNS优化篇)

[10] 爱奇艺APP网络优化实践(网络请求成功率优化篇)

[11] 美团点评的网络优化实践(大幅提升连接成功率、速度等)

[12] 淘宝移动端统一网络库的架构演进和弱网优化实践

[13] 爱奇艺APP跨国弱网通信的优化实践

[14] 得物自研移动端弱网诊断工具的技术实践

[15] 携程APP移动网络优化实践(弱网识别篇)

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

posted @ 2025-11-25 10:45 Jack Jiang 阅读(69) | 评论 (0)编辑 收藏

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