Jack Jiang

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

2025年12月23日

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

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

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

posted @ 2026-03-02 21:56 Jack Jiang 阅读(42) | 评论 (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 阅读(34) | 评论 (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 阅读(33) | 评论 (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 阅读(45) | 评论 (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 阅读(53) | 评论 (0)编辑 收藏

     摘要: 本文由45岁老架构师尼恩分享,感谢作者,有修订和重新排版。1、引言你有没有想过,为什么 ChatGPT 的回答能逐字逐句地“流”出来?这一切的背后,都离不开一项关键技术——SSE(Server-Sent Events)! 本文从SSE(Server-Sent Events)技术的原理到示例代码,为你通俗易懂的讲解SSE技术的方方面面。2、A...  阅读全文

posted @ 2025-12-23 15:07 Jack Jiang 阅读(57) | 评论 (0)编辑 收藏

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