乐趣区

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

本文原作者 Chank,原题“如何设计一个亿级音讯量的 IM 零碎”,为了晋升内容品质,本次有订正和改变。

1、写有后面

本文将在亿级音讯量、分布式 IM 零碎这个技术前提下,剖析和总结实现这套零碎所须要把握的知识点,内容没有浅近的技术概念,尽量做到老手新手皆能读懂。

本文不会给出一套通用的 IM 计划,也不会评判某种架构的好坏,而是探讨设计 IM 零碎的常见难题跟业界的解决方案。

因为也没有所谓的通用 IM 架构计划,不同的解决方案都各有其优缺点,只有最满足业务的零碎才是一个好的零碎。

在人力、物力、工夫资源无限的前提下,通常须要做出很多衡量,此时,一个可能反对疾速迭代、不便扩大的 IM 零碎才是最优解。

学习交换:

  • 即时通讯 / 推送技术开发交换 5 群:215477170 [举荐]
  • 挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
  • 开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK

2、相干文章

与本文相似,以下两篇也非常适合同时浏览,有趣味能够一并学习。

  • 《一套亿级用户的 IM 架构技术干货(上篇):整体架构、服务拆分等》
  • 《一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等》

3、IM 常见术语

_0)_用户:零碎的使用者。

_1)_音讯:是指用户之间的沟通内容(通常在 IM 零碎中,音讯会有以下几类:文本音讯、表情音讯、图片音讯、视频音讯、文件音讯等等)。

_2)_会话:通常指两个用户之间因聊天而建设起的关联。

_3)_群:通常指多个用户之间因聊天而建设起的关联。

_4)_终端:指用户应用 IM 零碎的机器(通常有 Android 端、iOS 端、Web 端等等)。

_5)_未读数:指用户还没读的音讯数量。

_6)_用户状态:指用户以后是在线、离线还是挂起等状态。

_7)_关系链:是指用户与用户之间的关系,通常有单向的好友关系、双向的好友关系、关注关系等等(这里须要留神与会话的区别:用户只有在发动聊天时才产生会话,但关系并不需要聊天能力建设。对于关系链的存储,能够应用图数据库(Neo4j 等等),能够很天然地表白事实世界中的关系,易于建模)。

_8)_单聊:一对一聊天。

_9)_群聊:多人聊天。

_10)_客服:在电商畛域,通常须要对用户提供售前征询、售后征询等服务(这时,就须要引入客服来解决用户的征询)。

_11)_音讯分流:在电商畛域,一个店铺通常会有多个客服,此时决定用户的征询由哪个客服来解决就是音讯分流(通常音讯分流会依据一系列规定来确定音讯会分流给哪个客服,例如客服是否在线(客服不在线的话须要从新分流给另一个客服)、该音讯是售前征询还是售后征询、以后客服的忙碌水平等等)。

_12)_信箱:本文的信箱咱们指一个 Timeline、一个收发音讯的队列。

4、读扩散 vs 写扩散

IM 零碎里常常会波及到读扩散和写扩散这两个技术概念,咱们来看看。

4.1 读扩散

如上图所示: A 与每个聊天的人跟群都有一个信箱(有些博文会叫 Timeline,见《古代 IM 零碎中聊天音讯的同步和存储计划探讨》),A 在查看聊天信息的时候须要读取所有有新音讯的信箱。

须要留神与 Feeds 零碎的区别:在 Feeds 零碎中,每个人都有一个写信箱,写只须要往本人的写信箱里写一次就好了,读须要从所有关注的人的写信箱里读。但 IM 零碎里的读扩散通常是每两个相关联的人就有一个信箱,或者每个群一个信箱。

读扩散的长处:

  • 1)写操作(发消息)很轻量,不论是单聊还是群聊,只须要往相应的信箱写一次就好了;
  • 2)每一个信箱人造就是两个人的聊天记录,能够不便查看聊天记录跟进行聊天记录的搜寻。

读扩散的毛病:读操作(读音讯)很重,在简单业务下,一条读扩散音讯源须要简单的逻辑能力扩散成指标音讯。

4.2 写扩散

接下来看看写扩散。

如上图所示:在写扩散中,每个人都只从本人的信箱里读取音讯。

但写(发消息)的时候,对于单聊跟群聊解决如下:

  • 1)单聊:往本人的信箱跟对方的信箱都写一份音讯,同时,如果须要查看两个人的聊天历史记录的话还须要再写一份(当然,如果从集体信箱也能回溯出两个人的所有聊天记录,但这样效率会很低);
  • 2)群聊:须要往所有的群成员的信箱都写一份音讯,同时,如果须要查看群的聊天历史记录的话还须要再写一份。能够看出,写扩散对于群聊来说大大地放大了写操作。

PS:实际上群聊中音讯扩散是 IM 开发中的技术痛点,有趣味倡议具体浏览:《无关 IM 群聊技术实现的文章汇总》。

写扩散长处:

  • 1)读操作很轻量;
  • 2)能够很不便地做音讯的多终端同步。

写扩散毛病:写操作很重,尤其是对于群聊来说(因为如果群成员很多的话,1 条音讯源要扩散写成“成员数 -1”条指标音讯,这是很恐怖的)。

在 Feeds 零碎中:

  • 1)写扩散也叫:Push、Fan-out 或者 Write-fanout;
  • 2)读扩散也叫:Pull、Fan-in 或者 Read-fanout。

5、惟一 ID 的技术计划

5.1 基础知识

通常状况下,ID 设计次要有以下几大类:

  • 1)UUID;
  • 2)基于 Snowflake 算法的 ID 生成形式;
  • 3)基于申请 DB 步长的生成形式;
  • 4)基于 Redis 或者 DB 的自增 ID 生成形式;
  • 5)非凡的规定生成惟一 ID。
  • … …

具体的实现办法跟优缺点能够参考以下 IM 音讯 ID 的专题文章:

《IM 音讯 ID 技术专题(一):微信的海量 IM 聊天音讯序列号生成实际(算法原理篇)》
《IM 音讯 ID 技术专题(二):微信的海量 IM 聊天音讯序列号生成实际(容灾计划篇)》
《IM 音讯 ID 技术专题(三):解密融云 IM 产品的聊天音讯 ID 生成策略》
《IM 音讯 ID 技术专题(四):深度解密美团的分布式 ID 生成算法》
《IM 音讯 ID 技术专题(五):开源分布式 ID 生成器 UidGenerator 的技术实现》
《IM 音讯 ID 技术专题(六):深度解密滴滴的高性能 ID 生成器(Tinyid)》

在 IM 零碎中须要惟一 Id 的中央次要是:

  • 1)聊天会话 ID;
  • 2)聊天音讯 ID。

5.2 音讯 ID

咱们来看看在设计音讯 ID 时须要思考的三个问题。

5.2.1)音讯 ID 不递增能够吗?

咱们先看看不递增的话会怎么:

  • 1)应用字符串,节约存储空间,而且不能利用存储引擎的个性让相邻的音讯存储在一起,升高音讯的写入跟读取性能;
  • 2)应用数字,但数字随机,也不能利用存储引擎的个性让相邻的音讯存储在一起,会加大随机 IO,升高性能;而且随机的 ID 不好保障 ID 的唯一性。

因而,音讯 ID 最好是递增的。

5.2.3)全局递增 vs 用户级别递增 vs 会话级别递增:

全局递增:指音讯 ID 在整个 IM 零碎随着工夫的推移是递增的。全局递增的话个别能够应用 Snowflake(当然,Snowflake 也只是 worker 级别的递增)。此时,如果你的零碎是读扩散的话为了避免音讯失落,那每一条音讯就只能带上上一条音讯的 ID,前端依据上一条音讯判断是否有失落音讯,有音讯失落的话须要从新拉一次。

用户级别递增:指音讯 ID 只保障在单个用户中是递增的,不同用户之间不影响并且可能反复。典型代表:微信(见《微信的海量 IM 聊天音讯序列号生成实际(算法原理篇)》)。如果是写扩散零碎的话信箱工夫线 ID 跟音讯 ID 须要离开设计,信箱工夫线 ID 用户级别递增,音讯 ID 全局递增。如果是读扩散零碎的话感觉应用用户级别递增必要性不是很大。

会话级别递增:指音讯 ID 只保障在单个会话中是递增的,不同会话之间不影响并且可能反复。典型代表:QQ。

5.2.3)间断递增 vs 枯燥递增:

间断递增是指 ID 按 _1,2,3…n_ 的形式生成;而枯燥递增是指只有保障前面生成的 ID 比后面生成的 ID 大就能够了,不须要间断。

据我所知:QQ 的音讯 ID 就是在会话级别应用的间断递增,这样的益处是,如果失落了音讯,当下一条音讯来的时候发现 ID 不间断就会去申请服务器,防止失落音讯。

此时,可能有人会想,我不能用定时拉的形式看有没有音讯失落吗?当然不能,因为音讯 ID 只在会话级别间断递增的话那如果一个人有上千个会话,那得拉多少次啊,服务器必定是抗不住的。

对于读扩散来说,音讯 ID 应用间断递增就是一种不错的形式了。如果应用枯燥递增的话以后音讯须要带上前一条音讯的 ID(即聊天音讯组成一个链表),这样,能力判断音讯是否失落。

5.2.4)小结一下:

写扩散:信箱工夫线 ID 应用用户级别递增,音讯 ID 全局递增,此时只有保障枯燥递增就能够了。

读扩散:音讯 ID 能够应用会话级别递增并且最好是间断递增。

5.3 会话 ID

咱们来看看设计会话 ID 须要留神的问题。

其中,会话 ID 有种比较简单的生成形式——非凡的规定生成惟一 ID:即拼接 from_user_id 跟 to_user_id。

拼接逻辑能够像上面这样:

  • 1)如果 from_user_id 跟 to_user_id 都是 32 位整形数据的话能够很不便地用位运算拼接成一个 64 位的会话 ID,即:_conversation_id = ${from_user_id} << 32 | ${to_user_id}_(在拼接前须要确保值比拟小的用户 ID 是 from_user_id,这样任意两个用户发动会话能够很不便地晓得会话 ID);
  • 2)如果 from_user_id 跟 to_user_id 都是 64 位整形数据的话那就只能拼接成一个字符串了,拼接成字符串的话就比拟伤了,节约存储空间性能又不好。

前东家就是应用的下面第 1 种形式,第 1 种形式有个硬伤:随着业务在寰球的扩大,32 位的用户 ID 如果不够用须要扩大到 64 位的话那就须要大刀阔斧地改了。32 位整形 ID 看起来可能包容 21 亿个用户,但通常咱们为了避免他人晓得实在的用户数据,应用的 ID 通常不是间断的,这时 32 位的用户 ID 就齐全不够用了。该设计齐全依赖于用户 ID,不是一种可取的设计形式。

因而:会话 ID 的设计能够应用全局递增的形式,加一个映射表:保留 from_user_id、to_user_id 跟 conversation_id 的关系。

6、新息的“推模式 vs 拉模式 vs 推拉联合模式”

在 IM 零碎中,新音讯的获取通常会有三种可能的做法:

  • 1)推模式:有新音讯时服务器被动推给所有端(iOS、Android、PC 等);
  • 2)拉模式:由前端被动发动拉取音讯的申请,为了保障音讯的实时性,个别采纳推模式,拉模式个别用于获取历史音讯;
  • 3)推拉联合模式:有新音讯时服务器会先推一个有新音讯的告诉给前端,前端接管到告诉后就向服务器拉取音讯。

推模式简化图如下:

如上图所示:失常状况下,用户发的音讯通过服务器存储等操作后会推给接管方的所有端。

但推是有可能会失落的:最常见的状况就是用户可能会伪在线(是指如果推送服务基于长连贯,而长连贯可能曾经断开,即用户曾经掉线,但个别须要通过一个心跳周期后服务器能力感知到,这时服务器会谬误地认为用户还在线;伪在线是自己本人想的一个概念,没想到适合的词来解释)。因而如果单纯应用推模式的话,是有可能会失落音讯的。

PS:为什么会呈现作者所述“伪在线”这个问题,能够读一下《为什么说基于 TCP 的挪动端 IM 依然须要心跳保活?》。

推拉联合模式简化图如下:

能够应用推拉联合模式解决推模式可能会丢音讯的问题:即在用户发新音讯时服务器推送一个告诉,而后前端申请最新消息列表,为了避免有音讯失落,能够再每隔一段时间被动申请一次。能够看出,应用推拉联合模式最好是用写扩散,因为写扩散只须要拉一条工夫线的集体信箱就好了,而读扩散有 N 条工夫线(每个信箱一条),如果也定时拉取的话性能会很差。

7、业界的 IM 解决方案参考

后面理解了 IM 零碎的常见设计问题,接下来咱们再看看业界是怎么设计 IM 零碎的。

钻研业界的支流计划有助于咱们深刻了解 IM 零碎的设计。以下钻研都是基于网上曾经公开的材料,不肯定正确,大家仅作参考就好了。

7.1 微信

尽管微信很多根底框架都是自研,但这并不障碍咱们了解微信的架构设计。

从微信公开的《疾速裂变:见证微信弱小后盾架构从 0 到 1 的演进历程(二)》这篇文章能够看出,微信采纳的次要是:写扩散 + 推拉联合。因为群聊应用的也是写扩散,而写扩散很耗费资源,因而微信群有人数下限(目前是 500)。所以这也是写扩散的一个显著毛病,如果须要万人群就比拟难了。

从文中还能够看出,微信采纳了多数据中心架构:

▲ 图片援用自《疾速裂变:见证微信弱小后盾架构从 0 到 1 的演进历程(二)》

微信每个数据中心都是自治的,每个数据中心都有全量的数据,数据中心间通过自研的音讯队列来同步数据。

为了保证数据的一致性,每个用户都只属于一个数据中心,只能在本人所属的数据中心进行数据读写,如果用户连了其它数据中心则会主动疏导用户接入所属的数据中心。而如果须要拜访其它用户的数据那只须要拜访本人所属的数据中心就能够了。

同时,微信应用了三园区容灾的架构,应用 Paxos 来保证数据的一致性。

从微信公开的《微信的海量 IM 聊天音讯序列号生成实际(容灾计划篇)》这篇文章能够看出,微信的 ID 设计采纳的是:基于申请 DB 步长的生成形式 + 用户级别递增。

如下图所示:

▲ 图片援用自《微信的海量 IM 聊天音讯序列号生成实际(容灾计划篇)》

微信的序列号生成器由仲裁服务生成路由表(路由表保留了 uid 号段到 AllocSvr 的全映射),路由表会同步到 AllocSvr 跟 Client。如果 AllocSvr 宕机的话会由仲裁服务从新调度 uid 号段到其它 AllocSvr。

PS:微信团队分享了大量的技术材料,有趣味能够看看《QQ、微信技术分享 – 汇总》。

7.2 钉钉

钉钉公开的材料不多,从《阿里钉钉技术分享:企业级 IM 王者——钉钉在后端架构上的过人之处》这篇文章咱们只能晓得,钉钉最开始应用的是写扩散模型,为了反对万人群,起初貌似优化成了读扩散。

但聊到阿里的 IM 零碎,不得不提的是阿里自研的 Tablestore:个别状况下,IM 零碎都会有一个自增 ID 生成零碎,但 Tablestore 创造性地引入了主键列自增,即把 ID 的生成整合到了 DB 层,反对了用户级别递增(传统 MySQL 等 DB 只能反对表级自增,即全局自增),具体能够参考:《如何优化高并发 IM 零碎架构》。

PS:钉钉团队公开的技术很少,这是另一篇:《钉钉——基于 IM 技术的新一代企业 OA 平台的技术挑战(视频 +PPT)》,有趣味能够钻研钻研。

7.3 Twitter

什么?Twitter 不是 Feeds 零碎吗?这篇文章不是探讨 IM 的吗?

是的,Twitter 是 Feeds 零碎,但 Feeds 零碎跟 IM 零碎其实有很多设计上的共性,钻研下 Feeds 零碎有助于咱们在设计 IM 零碎时进行参考。再说了,钻研下 Feeds 零碎也没有害处,扩大下技术视线嘛。

Twitter 的自增 ID 设计预计大家都耳熟能详了,即赫赫有名的 Snowflake,因而 ID 是全局递增的。

从这个视频分享《How We Learned to Stop Worrying and Love Fan-In at Twitter》能够看出,Twitter 一开始应用的是写扩散模型,Fanout Service 负责扩散写到 Timelines Cache(应用了 Redis),Timeline Service 负责读取 Timeline 数据,而后由 API Services 返回给用户。

但因为写扩散对于大 V 来说写的耗费太大,因而前面 Twitter 又应用了写扩散跟读扩散联合的形式。

如下图所示:

对于粉丝数不多的用户如果发 Twitter 应用的还是写扩散模型,由 Timeline Mixer 服务将用户的 Timeline、大 V 的写 Timeline 跟零碎举荐等内容整合起来,最初再由 API Services 返回给用户。

7.4 58 到家

58 到家实现了一个通用的实时音讯平台:

▲ 图片援用自《58 到家实时音讯零碎的架构设计及技术选型经验总结》

能够看出:msg-server 保留了利用跟 MQ 主题之间的对应关系,msg-server 依据这个配置将音讯推到不同的 MQ 队列,具体的利用来生产就能够了。因而,新增一个利用只须要批改配置就能够了。

58 到家为了保障音讯投递的可靠性,还引入了确认机制:音讯平台收到音讯先落地数据库,接管方收到后应用层 ACK 再删除。应用确认机制最好是只能单点登录,如果多端可能同时登录的话那就比拟麻烦了,因为须要所有端都确认收到音讯后能力删除。

PS:58 到家平台部负责人任桃术还分享过《58 到家实时音讯零碎的协定设计等技术实际分享》一文,有趣味能够一并浏览。

看到这里,预计大家曾经明确了,设计一个 IM 零碎很有挑战性。咱们还是持续来看设计一个 IM 零碎须要思考的问题吧。

7.5 其它业界计划

即时通讯网也收录了大量其它的业界 IM 或类 IM 零碎的设计方案,限于篇幅起因这里就不一一列出,有趣味能够选择性地浏览,一下是文章汇总。

《一套海量在线用户的挪动端 IM 架构设计实际分享 (含具体图文)》
《一套原创分布式即时通讯(IM) 零碎实践架构计划》
《从零到卓越:京东客服即时通讯零碎的技术架构演进历程》
《蘑菇街即时通讯 /IM 服务器开发之架构抉择》
《古代 IM 零碎中聊天音讯的同步和存储计划探讨》
《WhatsApp 技术实际分享:32 人工程团队发明的技术神话》
《微信朋友圈千亿访问量背地的技术挑战和实际总结》
《以微博类利用场景为例,总结海量社交零碎的架构设计步骤》
《子弹短信光鲜的背地:网易云信首席架构师分享亿级 IM 平台的技术实际》
《一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实际》
《从游击队到正规军(一):马蜂窝旅游网的 IM 零碎架构演进之路》
《从游击队到正规军(三):基于 Go 的马蜂窝旅游网分布式 IM 零碎技术实际》
《瓜子 IM 智能客服零碎的数据架构设计(整顿自现场演讲,有配套 PPT)》
《阿里技术分享:电商 IM 音讯平台,在群聊、直播场景下的技术实际》
《一套亿级用户的 IM 架构技术干货(上篇):整体架构、服务拆分等》

8、IM 须要解决的技术痛点

8.1 如何保障音讯的实时性

PS:如果你还不理解 IM 里的音讯实时性是什么,务必先读这篇《零根底 IM 开发入门(二):什么是 IM 零碎的实时性?》;

在通信协议的抉择上,咱们次要有以下几个抉择:

  • 1)应用 TCP Socket 通信,本人设计协议:58 到家等等;
  • 2)应用 UDP Socket 通信:QQ 等等(见《为什么 QQ 用的是 UDP 协定而不是 TCP 协定?》);
  • 3)应用 HTTP 长轮循:微信网页版等等。

不论应用哪种形式,咱们都可能做到音讯的实时告诉,但影响咱们音讯实时性的可能会在咱们解决音讯的形式上。

例如:如果咱们推送的时候应用 MQ 去解决并推送一个万人群的音讯,推送一个人须要 2ms,那么推完一万人须要 20s,那么前面的音讯就阻塞了 20s。如果咱们须要在 10ms 内推完,那么咱们推送的并发度应该是:_人数:10000 / (推送总时长:10 / 单个人推送时长:2) = 2000_。

因而:咱们在抉择具体的实现计划的时候肯定要评估好咱们零碎的吞吐量,零碎的每一个环节都要进行评估压测。只有把每一个环节的吞吐量评估好了,能力保障音讯推送的实时性。

IM 音讯实时性中群聊音讯和单聊音讯的解决又有很大区别,有趣味能够深刻浏览:

《IM 音讯送达保障机制实现(一):保障在线实时音讯的牢靠投递》
《挪动端 IM 中大规模群音讯的推送如何保障效率、实时性?》

8.2 如何保障音讯时序

在 IM 的技术实现中,以下状况下音讯可能会乱序(提醒:如果你还不理解什么是 IM 的音讯时序,务必先浏览《零根底 IM 开发入门(四):什么是 IM 零碎的音讯时序一致性?》)。

8.2.1)发送音讯如果应用的不是长连贯,而是应用 HTTP 的话可能会呈现乱序:

因为后端个别是集群部署,应用 HTTP 的话申请可能会打到不同的服务器,因为网络提早或者服务器处理速度的不同,后发的音讯可能会先实现,此时就产生了音讯乱序。

解决方案:

  • 1)前端顺次对音讯进行解决,发送完一个音讯再发送下一个音讯。这种形式会升高用户体验,个别状况下不倡议应用;
  • 2)带上一个前端生成的程序 ID,让接管方依据该 ID 进行排序。这种形式前端解决会比拟麻烦一点,而且聊天的过程中接管方的历史音讯列表中可能会在两头插入一条音讯,这样会很奇怪,而且用户可能会漏读音讯。但这种状况能够通过在用户切换窗口的时候再进行重排来解决,接管方每次收到音讯都先往最初面追加。

8.2.2)通常为了优化体验,有的 IM 零碎可能会采取异步发送确认机制(例如:QQ):

即音讯只有达到服务器,而后服务器发送到 MQ 就算发送胜利。如果因为权限等问题发送失败的话后端再推一个告诉上来。

这种状况下 MQ 就要抉择适合的 Sharding 策略了:

  • 1)按 to_user_id 进行 Sharding:应用该策略如果须要做多端同步的话发送方多个端进行同步可能会乱序,因为不同队列的处理速度可能会不一样。例如发送方先发送 m1 而后发送 m2,但服务器可能会先解决完 m2 再解决 m1,这里其它端会先收到 m2 而后是 m1,此时其它端的会话列表就乱了;
  • 2)按 conversation_id 进行 Sharding:应用该策略同样会导致多端同步会乱序;
  • 3)按 from_user_id 进行 Sharding:这种状况下应用该策略是比拟好的抉择。

通常为了优化性能,推送前可能会先往 MQ 推,这种状况下应用 to_user_id 才是比拟好的抉择。

PS:实际上,导致 IM 音讯乱序的可能性还有很多,这里就不一一开展,以下几篇值得深刻浏览。

《如何保障 IM 实时音讯的“时序性”与“一致性”?》
《一个低成本确保 IM 音讯时序的办法探讨》
《一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等》

8.3 用户在线状态如何做

很多 IM 零碎都须要展现用户的状态:是否在线,是否繁忙等。

要实现用户在线状态的存储,次要能够应用:

  • 1)Redis;
  • 2)分布式一致性哈希来。

Redis 存储用户在线状态:

看下面的图可能会有人纳闷:为什么每次心跳都须要更新 Redis?

如果我应用的是 TCP 长连贯那是不是就不必每次心跳都更新了?

的确:失常状况下服务器只须要在新建连贯或者断开连接的时候更新一下 Redis 就好了。但因为服务器可能会出现异常,或者服务器跟 Redis 之间的网络会呈现问题,此时基于事件的更新就会呈现问题,导致用户状态不正确。因而,如果须要用户在线状态精确的话最好通过心跳来更新在线状态。

因为 Redis 是单机存储的,因而,为了进步可靠性跟性能,咱们能够应用 Redis Cluster 或者 Codis。

分布式一致性哈希存储用户在线状态:

应用分布式一致性哈希须要留神在对 Status Server Cluster 进行扩容或者缩容的时候要先对用户状态进行迁徙,不然在刚操作时会呈现用户状态不统一的状况。同时还须要应用虚构节点防止数据歪斜的问题。

PS:用户状态在客户端的更新也是个很有挑战性的问题,有趣味能够读一下《IM 单聊和群聊中的在线状态同步应该用“推”还是“拉”?》。

8.4 多端同步怎么做

8.4.1)读扩散:

后面也提到过:对于读扩散,音讯的同步次要是以推模式为主,单个会话的音讯 ID 程序递增,前端收到推的音讯如果发现音讯 ID 不间断就申请后端从新获取音讯。

但这样依然可能失落会话的最初一条音讯。

为了加大音讯的可靠性:能够在历史会话列表的会话里再带上最初一条音讯的 ID,前端在收到新音讯的时候会先拉取最新的会话列表,而后判断会话的最初一条音讯是否存在,如果不存在,音讯就可能失落了,前端须要再拉一次会话的音讯列表;如果会话的最初一条音讯 ID 跟音讯列表里的最初一条音讯 ID 一样,前端就不再解决。

这种做法的性能瓶颈会在拉取历史会话列表那里,因为每次新音讯都须要拉取后端一次,如果按微信的量级来看,单是音讯就可能会有 20 万的 QPS,如果历史会话列表放到 MySQL 等传统 DB 的话必定抗不住。

因而,最好将历史会话列表存到开了 AOF(用 RDB 的话可能会丢数据)的 Redis 集群。这里只能感叹性能跟简略性不能兼得。

8.4.2)写扩散:

对于写扩散来说,多端同步就简略些了。前端只须要记录最初同步的位点,同步的时候带上同步位点,而后服务器就将该位点前面的数据全副返回给前端,前端更新同步位点就能够了。

PS:多端同步这也是 IM 里比拟坑爹的技术痛点,有趣味请移步《浅谈挪动端 IM 的多点登录和音讯漫游原理》。

8.5 如何解决未读数

在 IM 零碎中,未读数的解决十分重要。

未读数个别分为会话未读数跟总未读数,如果处理不当,会话未读数跟总未读数可能会不统一,重大升高用户体验。

8.5.1)读扩散:

对于读扩散来说,咱们能够将会话未读数跟总未读数都存在后端,但后端须要保障两个未读数更新的原子性跟一致性。

个别能够通过以下两种办法来实现:

  • 1)应用 Redis 的 multi 事务性能,事务更新失败能够重试。但要留神如果你应用 Codis 集群的话并不反对事务性能;
  • 2)应用 Lua 嵌入脚本的形式。应用这种形式须要保障会话未读数跟总未读数都在同一个 Redis 节点(Codis 的话能够应用 Hashtag)。这种形式会导致实现逻辑扩散,加大保护老本。

8.5.2)写扩散:

对于写扩散来说,服务端通常会弱化会话的概念,即服务端不存储历史会话列表。

未读数的计算可由前端来负责,标记已读跟标记未读能够只记录一个事件到信箱里,各个端通过重放该事件的模式来解决会话未读数。

应用这种形式可能会造成各个端的未读数不统一,至多微信就会有这个问题(Jack Jiang 注:实际上 QQ 也同样有这个问题,在分布式和多端 IM 中这的确是个很头痛的问题,大家都不会例外,哈哈 ~~)。

如果写扩散也通过历史会话列表来存储未读数的话那用户工夫线服务跟会话服务紧耦合,这个时候须要保障原子性跟一致性的话那就只能应用分布式事务了,会大大降低零碎的性能。

8.6 如何存储历史音讯

读扩散:对于读扩散,只须要按会话 ID 进行 Sharding 存储一份就能够了。

写扩散:对于写扩散,须要存储两份,一份是以用户为 Timeline 的音讯列表,一份是以会话为 Timeline 的音讯列表。以用户为 Timeline 的音讯列表能够用用户 ID 来做 Sharding,以会话为 Timeline 的音讯列表能够用会话 ID 来做 Sharding。

PS:如果你对 Timeline 这个概念不相熟,请读这篇《古代 IM 零碎中聊天音讯的同步和存储计划探讨》。

8.7 数据冷热拆散

对于 IM 来说,历史音讯的存储有很强的工夫序列个性,工夫越久,音讯被拜访的概率也越低,价值也越低。

如果咱们须要存储几年甚至是永恒的历史音讯的话(电商 IM 中比拟常见),那么做历史音讯的冷热拆散就十分有必要了。

数据的冷热拆散个别是 HWC(Hot-Warm-Cold)架构。

对于刚发送的音讯能够放到 Hot 存储系统(能够用 Redis)跟 Warm 存储系统,而后由 Store Scheduler 依据肯定的规定定时将冷数据迁徙到 Cold 存储系统。

获取音讯的时候须要顺次拜访 Hot、Warm 跟 Cold 存储系统,由 Store Service 整合数据返回给 IM Service。

微信团队分享的这篇《微信后盾基于工夫序的海量数据冷热分级架构设计实际》,或者能够有些启发。

8.8 接入层怎么做

对于散布于 IM 来说,接入层是必须要思考的的。

实现接入层的负载平衡次要有以下几个办法:

  • 1)硬件负载平衡:例如 F5、A10 等等。硬件负载平衡性能弱小,稳定性高,但价格十分贵,不是土豪公司不倡议应用;
  • 2)应用 DNS 实现负载平衡:应用 DNS 实现负载平衡比较简单,但应用 DNS 实现负载平衡如果须要切换或者扩容那失效会很慢,而且应用 DNS 实现负载平衡反对的 IP 个数有限度、反对的负载平衡策略也比较简单;
  • 3)DNS + 4 层负载平衡 + 7 层负载平衡架构:例如 DNS + DPVS + Nginx 或者 DNS + LVS + Nginx;
  • 4)DNS + 4 层负载平衡:4 层负载平衡个别比较稳定,很少改变,比拟适宜于长连贯。

对于第 3)点:有人可能会纳闷为什么要退出 4 层负载平衡呢?

这是因为 7 层负载平衡很耗 CPU,并且常常须要扩容或者缩容,对于大型网站来说可能须要很多 7 层负载平衡服务器,但只须要大量的 4 层负载平衡服务器即可。因而,该架构对于 HTTP 等短连贯大型利用很有用。

当然,如果流量不大的话只应用 DNS + 7 层负载平衡即可。但对于长连贯来说,退出 7 层负载平衡 Nginx 就不大好了。因为 Nginx 常常须要改配置并且 reload 配置,reload 的时候 TCP 连贯会断开,造成大量掉线。

对于长连贯的接入层,如果咱们须要更加灵便的负载平衡策略或者须要做灰度的话,那咱们能够引入一个调度服务。

如下图所示:

Access Schedule Service 能够实现依据各种策略来调配 Access Service。

例如:

  • 1)依据灰度策略来调配;
  • 2)依据就近准则来调配;
  • 3)依据起码连接数来调配。

9、写在最初

看完下面的内容你应该能粗浅领会到,要实现一个稳固牢靠的大用户量分布式 IM 零碎难度是相当大的,所谓路漫漫其修远兮。。。

在一直谋求体验更好、性能更高、负载更多、老本更低的能源下,IM 架构优化这条路是没有止境的,所以为了延缓程序员发量较少的焦虑,大家代码肯定要悠着点撸,头凉是很好受滴 ~~

PS:本篇次要是从 IM 架构设计这个角度来讲的,对于 IM 初学者来说是不容易看的明确,倡议初学者从这篇开始:《新手入门一篇就够:从零开发挪动端 IM》。

10、参考资料

[1] 58 到家实时音讯零碎的架构设计及技术选型经验总结

[2] 一套海量在线用户的挪动端 IM 架构设计实际分享(含具体图文)

[3] 古代 IM 零碎中聊天音讯的同步和存储计划探讨

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

[5] IM 音讯 ID 技术专题(二):微信的海量 IM 聊天音讯序列号生成实际(容灾计划篇)

[6] 疾速裂变:见证微信弱小后盾架构从 0 到 1 的演进历程(二)

本文已同步公布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步公布链接是:http://www.52im.net/thread-3472-1-1.html

退出移动版