关于即时通讯:跟着源码学IM十一一套基于Netty的分布式高可用IM详细设计与实现有源码

90次阅读

共计 17460 个字符,预计需要花费 44 分钟才能阅读完成。

本文由 will 分享,集体博客 zhangyaoo.github.io,原题“基于 Netty 的 IM 零碎设计与实现”,有订正和从新排版。

1、引言

本文将要分享的是如何从零实现一套基于 Netty 框架的分布式高可用 IM 零碎,它将反对长连贯网关治理、单聊、群聊、聊天记录查问、离线音讯存储、音讯推送、心跳、分布式惟一 ID、红包、音讯同步等性能,并且还反对集群部署。本文中针对这套架构和零碎设计,同时还会提供残缺的源码,比拟适宜有肯定 Java 开发能力和 Netty 常识的 IM 初学者。* 情谊提醒:如果你对 IM 即时通讯的根底技术实践理解的太少,倡议能够先读:《新手入门一篇就够:从零开发挪动端 IM》。

 技术交换:

  • 挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
  • 开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)(本文已同步公布于:http://www.52im.net/thread-4257-1-1.html)

2、配套源码

本文配套源码的开源托管地址是:
1)主地址:https://github.com/zhangyaoo/fastim2)
备地址:https://github.com/52im/fastim2023
如果你拜访 Github 太慢,可间接从以下附件打包下载:
fastim-master(52im.net).zip (1.12 MB , 下载次数: 5 , 售价: 1 金币)
残缺源码的目录构造,如下图:

3、常识筹备

对于 Netty 是什么,这里简略介绍下:Netty 是一个 Java 开源框架。Netty 提供异步的、事件驱动的网络应用程序框架和工具,用以疾速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于 NIO 的客户、服务器端编程框架,应用 Netty 能够确保你疾速和简略的开发出一个网络应用,例如实现了某种协定的客户,服务端利用。Netty 相当简化和流线化了网络应用的编程开发过程,例如,TCP 和 UDP 的 Socket 服务开发。

无关 Netty 的入门文章:
1)新手入门:目前为止最透彻的的 Netty 高性能原理和框架架构解析
2)写给初学者:Java 高性能 NIO 框架 Netty 的学习办法和进阶策略
3)史上最艰深 Netty 框架入门长文:根本介绍、环境搭建、入手实战如果你连 Java NIO 都不晓得,上面的文章倡议优先读:
1)少啰嗦!一分钟带你读懂 Java 的 NIO 和经典 IO 的区别
2)史上最强 Java NIO 入门:放心从入门到放弃的,请读这篇!
3)Java 的 BIO 和 NIO 很难懂?用代码实际给你看,再不懂我转行!

Netty 源码和 API 在线查阅地址:
1)Netty-4.1.x 残缺源码(在线浏览版)
2)Netty-4.1.x API 文档(在线版)

4、整体架构设计概览

本次的 IM 零碎设计次要基于可扩展性高可用准则,把网关层、逻辑层、数据层进行了拆散,并且还要反对分布式部署。以下是整体零碎的架构设计概览图:

上面将针对整体架构来逐个分享设计的次要思路等。

5、整体架构设计之客户端设计

5.1 客户端设计

客户端的设计次要从以下几点登程:
1)client 每个设施会在本地存每一个会话,保留有最新一条音讯的程序 ID;
2)为了防止 client 宕机,也就是退出利用,保留在内存的音讯 ID 失落,会存到本地的文件中;
3)client 须要在本地保护一个期待 ack 队列,并配合 timer 超时机制,来记录哪些音讯没有收到 ack:N,以定时重发;
4)客户端本地生成一个递增序列号发送给服务器,用作保障发送程序性。该序列号还用作 ack 队列收音讯时候的移除。

5.2 客户端序列号设计

1)计划一:

设计思路:1)数据传输中的大小尽量小用 int,不必 bigint,节俭传输大小;
2)只保障递增即可, 在用户从新登录或者重连后能够进行日期重置,只保障单次;
3)客户端发号器不须要像相似服务器端发号器那样集群部署,不须要思考集群同步问题。
注:上述生成器能够用 18 年 [(2^29-1)/3600/24/365] 左右,一秒内最多产生 4 个音讯。
长处:能够在断线重连和重装 APP 的状况下,18 年之内是有序的。
毛病:每秒只能发 4 个音讯,限度太大,对于群发场景不适合。改良:应用 long 进行传输,年限扩大很久并且有序。
2)计划二:设计思路:
1)每次从新建设链接后进行重置,将 sequence_id(int 示意)从 0 开始进行严格递增;
2)客户端发送音讯会带上惟一的递增 sequence_id,同一条音讯反复投递的 sequence_id 是一样的;
3)后端存储每个用户的 sequence_id,当 sequence_id 归 0,用户的 epoch 年代加 1 存储入库,单聊场景下转发给接收者时候,接收者依照 sequence_id 和 epoch 来进行排序。长处:能够在断线重连和重装 APP 的状况下,接收者能够依照发送者发送时序来显示,并且对发送音讯的速率没限度。

6、整体架构设计之 LSB 设计

6.1 思路

IM 接入层的高可用、负载平衡、扩展性全副在这外面做。客户端通过 LSB,来获取 gate IP 地址,通过 IP 直连。这样做的目标是:
1)灵便的负载平衡策略 可依据起码连接数来调配 IP;
2)做灰度策略来调配 IP;
3)AppId 业务隔离策略 不同业务连贯不同的 gate,避免相互影响;
4)单聊和群聊的 im 接入层通道离开。

6.2 优化

上述设计存在一个问题:就是当某个实例重启后,该实例的连贯断开后,客户端会发动重连,重连就大概率转移其余实例上,导致最近启动的实例连接数较少,最早启动的实例连接数较多。
解决办法:
1)客户端会发动重连,跟服务器申请重连的新的服务器 IP,零碎提供适合的算法来平摊 gate 层的压力,避免雪崩效应;
2)gate 层定时上报本机的元数据信息以及连接数信息,提供给 LSB 核心,LSB 依据起码连接数负载平衡实现,来计算一个节点供连贯。

7、整体架构设计之 GATE 层网关设计

GATE 层网关设计次要听从以下几点:
1)任何一个 gate 网关断掉,用户端检测到当前从新连贯 LSB 服务获取另一个 gate 网关 IP,拿到 IP 从新进行长连贯通信(对整体服务可靠性根本没有影响);
2)gate 能够无状态的横向部署,来扩大接入层的接入能力;
3)依据协定分类将入口申请打到不同的网关下来,HTTP 网关接管 HTTP 申请,TCP 网关接管 tcp 长连贯申请;
4)长连贯网关,提供各种监控性能,比方网关执行线程数、队列工作数、ByteBuf 应用堆内存数、堆外内存数、音讯上行和上行的数量以及工夫。

8、整体架构设计之 LOGIC 和路由 SDK 设计

logic 依照散布式微服务的拆分思维进行拆分,拆分为多个模块,集群部署。
次要包含:
1)音讯服务;
2)红包服务;
3)其余服务。
音讯 logic 服务集成路由客户端的 SDK,SDK 职责次要是:
1)负责和网关底层通信交互;
2)负责网关服务寻址;
3)负责存储 uid 和 gate 层机器 ID 关系(有状态:多级缓存防止和中间件屡次交互。无状态:在业务初期能够不必存);
4)配合网关负责路由信息一致性保障。
针对上述第 4)点:
1)如果路由状态和 channel 通道不统一,比方有路由状态,没有 channel 通道(已敞开)那么,就会走离线音讯流出,并且革除路由信息;
2)动静重启 gate,会及时清理路由信息。
SDK 和网关底层通信设计:

如上图所示:网关层到服务层,只须要单向传输发申请,网关层不须要关怀调用的后果。而客户端想要的 ack 或者 notify 申请是由 SDK 发送数据到网关层,SDK 也不须要关怀调用的后果,最初网关层只转发数据,不做额定的逻辑解决。SDK 和所有的网关进行长连贯,当发送信息给客户端时,依据路由寻址信息,即可通过长连贯推送信息。

9、通信协议设计

9.1 指标

通信协议设计的次要指标是:
1)高性能:协定设计紧凑,保障数据包小,并且序列化性能好;
2)可扩大:针对后续业务倒退,能够自在的自定义协定,无需较大改变协定构造。

9.2 设计

IM 协定采纳二进制定长包头和变长包体来实现客户端和服务端的通信,并且采纳谷歌 protobuf 序列化协定。设计如下:

各个字段解释如下:
1)headData:头部标识,协定头标识,用作粘包半包解决。4 个字节;
2)version:客户端版本。4 个字节;
3)cmd:业务命令,比方心跳、推送、单聊、群聊。1 个字节;
4)msgType:音讯告诉类型 request response notify。1 个字节;
5)logId:调试性日志,追溯一个申请的全门路。4 个字节;
6)sequenceId:序列号,能够用作异步解决。4 个字节;
7)dataLength:数据体的长度。4 个字节;
8)data:数据。
PS:如果你对 Protobuf 不理解,倡议详读以下系列文章:
1.《强列倡议将 Protobuf 作为你的即时通讯利用数据传输格局》
2.《IM 通信协定专题学习(一):Protobuf 从入门到精通,一篇就够!》
3.《IM 通信协定专题学习(二):疾速了解 Protobuf 的背景、原理、应用、优缺点》
4.《IM 通信协定专题学习(三):由浅入深,从根上了解 Protobuf 的编解码原理》
5.《IM 通信协定专题学习(四):从 Base64 到 Protobuf,详解 Protobuf 的数据编码原理》
6.《IM 通信协定专题学习(五):Protobuf 到底比 JSON 快几倍?全方位实测!》
7.《IM 通信协定专题学习(六):手把手教你如何在 Android 上从零应用 Protobuf》
8.《IM 通信协定专题学习(七):手把手教你如何在 NodeJS 中从零应用 Protobuf》
9.《IM 通信协定专题学习(八):金蝶顺手记团队的 Protobuf 利用实际(原理篇)》
10.《IM 通信协定专题学习(九):手把手教你如何在 iOS 上从零应用 Protobuf》

9.3 实际

针对数据 data,网关 gate 层不做反序列化,反序列化步骤在 service 做,防止反复序列化和反序列化导致的性能损失。网关层不做业务逻辑解决,只做音讯转发和推送,缩小网关层的复杂度。

10、平安设计

为避免音讯传输过程中不被截获、篡改、伪造,采纳 TLS 传输层加密协议(可参考《微信新一代通信安全解决方案:基于 TLS1.3 的 MMTLS 详解》)。私有化协定人造具备肯定的防窃取和防篡改的能力,绝对于应用 JSON、XML、HTML 等明文传输零碎,被第三方截获后在内容破解上绝对老本更高,因而安全性上会更好一些。音讯存储安全性:将针对账号密码的存储平安能够通过“高强度单向散列算法”和“加盐”机制来晋升加密明码可逆性;IM 音讯采纳“端到端加密”形式来提供更加平安的音讯传输爱护。平安层协定设计:基于动静密钥,借鉴相似 SSL,不须要用证书来治理(可参考《探讨组合加密算法在 IM 中的利用》)。

11、音讯投递设计

11.1 概述

一个失常的音讯流转须要如下图所示的流程:

如上图所示:
1)客户端 A 发送申请包 R;
2)server 将音讯存储到 DB;
3)存储胜利后返回确认 ack;
4)server push 音讯给客户端 B;
5)客户端 B 收到音讯后返回确认 ack;
6)server 收到 ack 后更新音讯的状态或者删除音讯。
须要思考的是:一个强壮的 IM 零碎须要思考各种异常情况,比方丢音讯,反复音讯,音讯时序问题。

11.2 音讯可靠性如何保障(不丢音讯)

我的设计和实现思路是这样的:
1)应用层 ACK;
2)客户端须要超时与重传;
3)服务端须要超时与重传,具体做法就是减少 ack 队列和定时器 Timer;
4)业务侧兜底保障,客户端拉音讯通过一个本地的旧的序列号来拉取服务器的最新消息;
5)为了保障音讯必达,在线客户端还减少一个定时器,定时向服务端拉取音讯,防止服务端向客户端发送拉取告诉的包失落导致客户端未及时拉取数据。

相干材料可参考:
1.《从客户端的角度来谈谈挪动端 IM 的音讯可靠性和送达机制》
2.《IM 音讯送达保障机制实现(一):保障在线实时音讯的牢靠投递》
3.《IM 音讯送达保障机制实现(二):保障离线音讯的牢靠投递》
4.《IM 开发干货分享:如何优雅的实现大量离线音讯的牢靠投递》
5.《了解 IM 音讯“可靠性”和“一致性”问题,以及解决方案探讨》
6.《融云技术分享:全面揭秘亿级 IM 音讯的牢靠投递机制》

11.3 音讯重复性如何保障(不反复)

超时与重传机制将导致接管的 client 收到反复的音讯,具体做法就是一份音讯应用同一个音讯 ID 进行去重解决。
相干材料可参考:
1.《IM 群聊音讯如此简单,如何保障不丢不重?》
2.《齐全自已开发的 IM 该如何设计“失败重试”机制?》

11.4 音讯程序性如何保障(不乱序)音讯乱序影响的因素:

1)时钟不统一,分布式环境下每个机器的工夫可能是不统一的;
2)多发送方和多接管方,这种状况下,无奈保先发的音讯被先收到;
3)网络传输和多线程,网络传输不稳固的话可能导致包在数据传输过程中有的慢有的快。多线程也可能是会导致时序不统一影响的因素。
以上:如果放弃相对的实现,那么只能是一个发送方,一个接管方,一个线程阻塞式通信来实现。那么性能会升高。
1)如何保障时序:单聊:通过发送方的相对时序 seq,来作为接管方的展示时序 seq。实现形式:能够通过工夫戳或者本地序列号形式来实现毛病:本地工夫戳不精确或者本地序列号在意外状况下可能会清 0,都会导致发送方的相对时序不精确群聊:因为发送方多点发送时序不统一,所以通过服务器的单点做序列化,也就是通过 ID 递增发号器服务来生成 seq,接管方通过 seq 来进行展示时序。实现形式:通过服务端对立生成惟一趋势递增音讯 ID 来实现或者通过 redis 的递增 incr 来实现。毛病:redis 的递增 incr 来实现,redis 取号都是从主取的,会有性能瓶颈。ID 递增发号器服务是集群部署,可能不同发号服务上的集群工夫戳不同,可能会导致后到的音讯 seq 还小。群聊时序的优化:依照下面的群聊解决,业务上依照情理只须要保障单个群的时序,不须要保障所有群的相对时序,所以解决思路就是同一个群的音讯落到同一个发号 service 下面,音讯 seq 通过 service 本地生成即可。

2)客户端如何保障程序:为什么要保障程序?因为音讯即便依照程序达到服务器端,也会可能呈现:不同音讯达到接收端后,可能会呈现“先产生的音讯后到”“后产生的音讯先到”等问题。所以客户端须要进行兜底的流量整形机制如何保障程序?能够在接管方收到音讯后进行断定,如果以后音讯序号大于前一条音讯的序号就将以后音讯追加在会话里。否则持续往前查找倒数第二条、第三条等音讯,始终查找到恰好小于以后推送音讯的那条音讯,而后插入在其后展现。相干材料可参考:《零根底 IM 开发入门(四):什么是 IM 零碎的音讯时序一致性?》《一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等》《如何保障 IM 实时音讯的“时序性”与“一致性”?》《一个低成本确保 IM 音讯时序的办法探讨》

12、音讯告诉设计

12.1 概述

整体音讯推送和拉取的时序图如下:

12.2 音讯拉取形式的抉择

本零碎是通过推拉联合来进行服务器端音讯的推送和客户端的拉取。咱们晓得单 pull 和单 push 有以下毛病。
对于单 pull:
1)pull 要思考到音讯的实时性,不晓得音讯何时送达;
2)pull 要思考到哪些好友和群收到了音讯,要循环每个群和好友拿到音讯列表,读扩散。
对于单 push:
1)push 实时性高,只有将音讯推送给接收者就 ok,然而会集中耗费服务器资源;
2)并且再群聊十分多、聊天频率十分高的状况下,会减少客户端和服务端的网络交互次数。
对于推拉联合:
1)推拉联合的形式可能摊派服务端的压力, 能保障时效性,又能保障性能;
2)具体做法就是有新音讯时候,推送哪个好友或者哪个群有新音讯,以及新音讯的数量或者最新消息 ID,客户端按需依据本身数据进行拉取。

12.3 推拉隔离设计

为什么做隔离?如果客户端一边正在拉取数据,一边有新的增量音讯 push 过去。如何做隔离?本地设置一个全局的状态,当客户端拉取完离线音讯后设置状态为 1(示意离线音讯拉取结束)。当客户端收到拉取实时音讯,会启用一个轮询监听这个状态,状态为 1 后,再去向服务器拉取音讯。如果是 push 音讯过去(不是被动拉取),那么会先将音讯存储到本地的音讯队列中,期待客户端上一次拉取数据结束,而后将数据进行合并即可。

相干材料可参考:
《阿里 IM 技术分享(六):闲鱼亿级 IM 音讯零碎的离线推送达到率优化》
《阿里 IM 技术分享(七):闲鱼 IM 的在线、离线聊天数据同步机制优化实际》

13、音讯 ID 生成设计以下是我设计的场景:

1)单机顶峰并发量小于 1W,预计将来 5 年单机顶峰并发量小于 10W;
2)有 2 个机房,预计将来 5 年机房数量小于 4 个 每个机房机器数小于 150 台;
3)目前只有单聊和群聊两个业务线,后续能够扩大为零碎音讯、聊天室、客服等业务线,最多 8 个业务线。
依据以上业务状况,来设计分布式 ID:

长处:
1)不同机房不同机器不同业务线内生成的 ID 互不雷同;
2)每个机器的每毫秒内生成的 ID 不同;
3)预留两位留作扩大位。
毛病:当并发度不高的时候,工夫跨毫秒的音讯,辨别不进去音讯的先后顺序。因为工夫跨毫秒的音讯生成的 ID 前面的最初一位都是 0,后续如果依照音讯 ID 维度进行分库分表,会导致数据歪斜。

两种解决方案:
1)计划一:去掉 snowflake 最初 8 位,而后对残余的位进行取模;
2)计划二:不同毫秒的计数,每次不是归 0,而是归为随机数,相比计划一,比较简单实用。

相干材料可参考:
《微信的海量 IM 聊天音讯序列号生成实际(算法原理篇)》
《微信的海量 IM 聊天音讯序列号生成实际(容灾计划篇)》
《解密融云 IM 产品的聊天音讯 ID 生成策略》《深度解密美团的分布式 ID 生成算法》
《开源分布式 ID 生成器 UidGenerator 的技术实现》
《深度解密滴滴的高性能 ID 生成器(Tinyid)》

14、音讯未读数设计

14.1 根本实现思路大抵如下:

1)每发一个音讯,音讯接收者的会话未读数 +1,并且接收者所有未读数 +1;
2)音讯接收者返回音讯接管确认 ack 后,音讯未读数会 -1;
3)音讯接收者的未读数 +1,服务端就会推算有多少条未读数的告诉。

分布式锁保障总未读数和会话未读数统一:
1)起因:当总未读数减少,这个时候客户端来了申请将未知数置 0,而后再减少会话未读数,那么会导致不统一;
2)保障:为了保障总未读数和会话未读数原子性,须要用分布式锁来保障。

14.2 群聊音讯未读数的难点和优化思路

对于群聊来说,音讯未读数的技术难点次要是:一个群聊每秒几百的并发聊天,比方音讯未读数,相当于每秒 W 级别的写入 redis,即使 redis 做了集群数据分片 + 主从,然而写入还是单节点,会有写入瓶颈。
我的优化思路是:按群 ID 分组或者用户 ID 分组,批量写入,写入的两种形式:定时 flush 和满多少音讯进行 flush。

15、网关设计

15.1 概述

本套 IM 零碎在设计时,将网关分为了接入层网关和应用层网关两种。
接入层网关和应用层网关区别次要是:
1)接入层网关须要有接管告诉包或者上行接收数据的端口,并且须要另外开启线程池。应用层网关不须要开始口,并且不须要开启线程池;
2)接入层网关须要放弃长连贯,接入层网关须要本地缓存 channel 映射关系。应用层网关无状态不须要保留。

15.2 接入层网关设计

我的设计指标是:
1)网关的线程池实现 1 +8+4+1,缩小线程切换;
2)集中实现长连贯治理和推送能力;
3)与业务服务器解耦,集群部署缩容扩容以及重启降级不相互影响;
4)长连贯的监控与报警能力;
5)客户端重连指令一键实现。
次要技术要点:
1)反对自定义协定以及序列化;
2)反对 websocket 协定;
3)通道连贯自定义保活以及心跳检测;
4)本地缓存 channel;
5)责任链;
6)服务调用齐全异步;
7)泛化调用;
8)转发告诉包或者 Push 包;
9)容错网关 down 机处理。
设计方案(一个 Notify 包的数据经网关的线程模型图):

15.3 应用层 API 网关设计

我的设计指标是:
1)基于版本的主动发现以及灰度 / 扩容,不须要关注 IP;
2)网关的线程池实现 1 +8+1,缩小线程切换;
3)反对协定转换实现多个协定转换,基于 SPI 来实现;
4)与业务服务器解耦,集群部署缩容扩容以及重启降级不相互影响;
5)接口错误信息统计和 RT 工夫的监控和报警能力;
6)UI 界面实现路由算法,服务接口版本治理,灰度策略管理以及接口和服务信息展现能力;
7)基于 OpenAPI 提供接口级别的主动生成文档的性能。

次要技术要点:
1)Http2.0;
2)channel 连接池复用;
3)Netty http 服务端编解码;
4)责任链;
5)服务调用齐全异步;
6)全链路超时机制;
7)泛化调用。
设计方案(一个申请包的数据经网关的架构图):

16、高并发设计

16.1 架构优化

次要从以下几个方面动手:
1)程度扩大:各个模块无状态部署;
2)线程模型:每个服务底层线程模型听从 Netty 主从 reactor 模型;
3)多层缓存:Gate 层二级缓存,Redis 一级缓存;
4)长连贯:客户端长连贯放弃,防止频繁创立连贯耗费。

16.2 万人群聊优化

技术难点次要是:音讯扇出大,比方每秒群聊有 50 条音讯,群聊 2000 人,那么光一个群对系统并发就有 10W 的音讯扇出。
优化思路:
1)批量 ACK:每条群音讯都 ACK,会给服务器造成微小的冲击,为了缩小 ACK 申请量,参考 TCP 的 Delay ACK 机制,在接管方层面进行批量 ACK;
2)群音讯和成员批量加载以及懒加载:在真正进入一个群时才实时拉取群友的数据;
3)群离线音讯过多:群音讯分页拉取, 第二次拉取申请作为第一次拉取申请的 ack;
4)对于音讯未读数场景,每个用户保护一个全局的未读数和每个会话的未读数,当群聊十分大时,未读资源变更的 QPS 十分大。这个时候应用层对未读数进行缓存,批量写 + 定时写来保障未读计数的写入性能;
5)路由信息存入 redis 会有写入和读取的性能瓶颈,每条音讯在收回的时候会查路由信息来发送对应的 gate 接入层,比方有 10 个群,每个群 1W,那么 1s100 条音讯,那么 1000W 的查问会打满 redis,即便 redis 做了集群。优化的思路就是将集中的路由信息扩散到 msg 层 JVM 本地内存中,而后做 Route 可用,防止单点故障;
6)存储的优化:扩散写写入并发量微小,另一方面也存在存储节约,个别优化成扩散读的形式存储;
7)音讯路由到雷同接入层机器进行合并申请缩小网络包传输。

相干材料:
1.《网易云信技术分享:IM 中的万人群聊技术计划实际总结》
2.《企业微信的 IM 架构设计揭秘:音讯模型、万人群、已读回执、音讯撤回等》
3.《融云 IM 技术分享:万人群聊音讯投递计划的思考和实际》

16.3 代码优化

具体的代码优化思路就是:本地会话信息由一个 hashmap 放弃,导致锁机制重大,依照用户标识进行 hash, 讲会话信息存在多个 map 中,缩小锁竞争。同时利用双 buffer 机制,防止未读计数写入阻塞。

16.4 推拉联合优化合并

背景:音讯下发到群聊服务后,须要发送拉取告诉给接收者,具体逻辑是群聊服务同步音讯到路由层,路由层发送音讯给接收者,接收者再来拉取音讯。问题:如果音讯间断发送或者对同一个接收者间断发送音讯频率过高,会有许多的告诉音讯发送给路由层,音讯量过大,可能会导致 logic 线程沉积,申请路由层阻塞。解决:发送者发送音讯到逻辑层长久化后,将告诉音讯先寄存一个队列中,雷同的接收者接管音讯告诉音讯后,更新相应的最新消息告诉工夫,而后轮训线程会轮训队列,将多个音讯会合并为一个告诉拉取发送至路由层,升高了客户端与服务端的网络耗费和服务器外部网络耗费。益处:保障同一时刻,下发线程一轮只会向同一用户发送一个告诉拉取,一轮的工夫能够自行管制。

17、高可用设计

17.1 心跳设计

次要是:
1)服务端检测到某个客户端迟迟没有心跳过去能够被动敞开通道,让它下线,并且革除在线信息和路由信息;
2)客户端检测到某个服务端迟迟没有响应心跳也能重连获取一个新的连贯。
智能心跳策略:比方正在发包的时候,不须要发送心跳。期待发包结束后在开启心跳。并且自适应心跳策略调整。
相干材料:
《为何基于 TCP 协定的挪动端 IM 依然须要心跳保活机制?》
《一文读懂即时通讯利用中的网络心跳包机制:作用、原理、实现思路等》
《微信团队原创分享:Android 版微信后盾保活实战分享(过程保活篇)》
《微信团队原创分享:Android 版微信后盾保活实战分享(网络保活篇)》
《融云技术分享:融云安卓端 IM 产品的网络链路保活技术实际》
《挪动端 IM 实际:实现 Android 版微信的智能心跳机制》
《万字长文:手把手教你实现一套高效的 IM 长连贯自适应心跳保活机制》

17.2 零碎稳定性

设计背景:高峰期零碎压力大,偶发的网络稳定或者机器过载,都有可能导致大量的零碎失败。加上 IM 零碎要求实时性,不能用异步解决实时发过来的音讯。所以有了柔性爱护机制避免雪崩。柔性爱护机制开启判断指标,当每个指标不在均匀范畴内的时候就开启。
这些判断指标次要是:
1)每条音讯的 ack 工夫 RT 工夫
2)同时在线人数以及同时发消息的人数
3)每台机器的负载 CPU 和内存和网络 IO 和磁盘 IO 以及 GC 参数当开启了柔性爱护机制,那么会返回失败,用户端体验不敌对,如何优化?
以下是我的优化思路:
1)当开启了柔性爱护机制,逻辑层 hold 住多余的申请,返回前端胜利,不显示发送失败,后端异步重试,直至胜利;
2)为了防止重试加剧零碎过载,指数时间延迟重试。

17.3 异样场景设计

gate 层重启降级或者意外 down 机有以下问题:
1)客户端和 gate 意外失落长连贯,导致 客户端在发送音讯的时候导致音讯超时期待以及客户端重试等无意义操作;
2)发送给客户端的音讯,从 Msg 音讯层转发给 gate 的音讯失落,导致音讯超时期待以及重试。
解决方案如下:
1)重启降级时候,向客户端发送从新连贯指令,让客户端从新申请 LSB 获取 IP 直连;
2)当 gate 层 down 机异样进行时候,减少 hook 钩子,向客户端发送从新连贯指令;
3)额定减少 hook,向 Msg 音讯层发送申请清空路由音讯和在线状态,并且革除 redis 的路由信息。

17.4Redis 宕机高可用设计

Redis 的作用背景:
1)当用户链接上网关后,网关会将用户的 userId 和机器信息存入 redis,用作这个 user 接管音讯时候,音讯的路由;
2)音讯服务在发消息给 user 时候,会查问 Redis 的路由信息,用来发送音讯给哪个一个网关。
如果 Redis 宕机,会造成上面后果:
1)音讯直达不过来,所有的用户能够发送音讯,然而都接管不了音讯;
2)如果有在线机制,那么零碎都认为是离线状态,会走手机音讯通道推送。
Redis 宕机兜底解决策略:
1)音讯服务定时工作同步路由信息到本地缓存,如果 redis 挂了,从本地缓存拿音讯;
2)网关服务在收到用户侧的上线和下线后,会同步播送本地的路由信息给各个音讯服务,音讯服务接管后更新本地环境数据;
3)网络交互次数多,以及音讯服务多,能够用批量或者定时的形式同步播送路由音讯给各个音讯服务。

18、外围表结构设计

外围设计要点:
1)群音讯只存储一份,用户不须要为每个音讯独自存一份。用户也无需去删除群音讯;
2)对于在线的用户,收到群音讯后,批改这个 last_ack_msg_id;
3)对于离线用户,用户上线后,比照最新的音讯 ID 和 last_ack_msg_id,来进行拉取(参考 Kafka 的消费者模型);
4)对应单聊,须要记录音讯的送达状态,以便在异常情况下来做重试解决。群用户音讯表 t_group_user_msg:

群音讯表 t_group_msg:

参考资料:
1.《一套海量在线用户的挪动端 IM 架构设计实际分享(含具体图文)》
2.《基于 Netty,从零开发一个 IM 服务端》

19、红包设计
抢红包的大抵外围逻辑如下:
1)银行快捷领取,保障账户余额和发送红包逻辑的一致性;
2)发送红包后,首先计算好红包的个数,个数确定好后,确定好每个红包的金额,存入存储层【这里能够是 redis 的 List 或者是队列】不便后续每个人来取;
3)生成一个 24 小时的提早工作,检测红包是否还有钱不便退回;
4)每个红包的金额须要保障每个红包的的抢金额概率是统一的,算法须要考量;
5)存入数据库表中后,服务器通过长连贯,给群里 notify 红包音讯, 供群成员抢红包;
6)群成员并发抢红包,在第二步中会将每个红包的金额放入一个队列或者其余存储中,群成员理论是来竞争去队列中的红包金额。兜底机制:如果 redis 挂了,能够从新生成红包信息到数据库中;7)取胜利后,须要保障红包残余金额、新插入的红包流水数据、队列中的红包数据以及群成员的余额账户金额一致性;
8)这里还须要保障一个用户只能支付一次,并且放弃幂等。
相干材料:
《社交软件红包技术解密(一):全面解密 QQ 红包技术计划——架构、技术实现等》
《社交软件红包技术解密(二):解密微信摇一摇红包从 0 到 1 的技术演进》
《社交软件红包技术解密(三):微信摇一摇红包雨背地的技术细节》
《社交软件红包技术解密(四):微信红包零碎是如何应答高并发的》
《社交软件红包技术解密(五):微信红包零碎是如何实现高可用性的》
《社交软件红包技术解密(六):微信红包零碎的存储层架构演进实际》
《社交软件红包技术解密(七):支付宝红包的海量高并发技术实际》
《社交软件红包技术解密(八):全面解密微博红包技术计划》
《社交软件红包技术解密(九):谈谈手 Q 红包的性能逻辑、容灾、运维、架构等》
《社交软件红包技术解密(十):手 Q 客户端针对 2020 年春节红包的技术实际》
《社交软件红包技术解密(十一):解密微信红包随机算法(含代码实现)》
《社交软件红包技术解密(十二):解密抖音春节红包背地的技术设计与实际》

20、外围业务流程梳理

20.1 单聊流程

假如是用户 A 发消息给用户 B,以下是残缺的业务流程。
1)A 打包数据发送给服务端,服务端接管音讯后,依据接管音讯的 sequence_id 来进行客户端发送音讯的去重,并且生成递增的音讯 ID,将发送的信息和 ID 打包一块入库,入库胜利后返回 ACK,ACK 包带上服务端生成的音讯 ID。
2)服务端检测接管用户 B 是否在线,在线间接推送给用户 B。
3)如果没有本地音讯 ID 则存入,并且返回接入层 ACK 信息;如果有则拿本地 sequence_id 和推送过去的 sequence_id 大小比照,并且去重,进行展示时序进行排序展现,并且记录最新一条音讯 ID。最初返回接入层 ack。
4)服务端接管 ACK 后,将音讯标为已送达。
5)如果用户 B 不在线, 首先将音讯存入库中,而后间接通过手机告诉来告知客户新音讯到来。
6)用户 B 上线后,拿本地最新的音讯 ID,去服务端拉取所有好友发送给 B 的音讯,思考到一次拉取所有音讯数据量大,通过 channel 通道来进行分页拉取,将上一次拉取音讯的最大的 ID,作为申请参数,来申请最新一页的比 ID 大的数据。

20.2 群聊流程

假如是用户 A 发消息给群 G,以下是残缺的业务流程。
1)登录,TCP 连贯,token 校验,名词查看,sequence_id 去重,生成递增的音讯 ID,群音讯入库胜利返回发送方 ACK。
2)查问群 G 所有的成员,而后去 redis 地方存储中找在线状态。离线和在线成员分不同的形式解决。3)在线成员:并行发送拉取告诉,期待在线成员过去拉取,发送拉取告诉包如失落会有兜底机制。4)在线成员过去拉取,会带上这个群标识和上一次拉取群的最小音讯 ID,服务端会找比这个音讯 ID 大的所有的数据返回给客户端,期待客户端 ACK。一段时间没 ack 持续推送。如果重试几次后没有回 ack,那么敞开连贯和革除 ack 期待队列音讯。
5)客户端会更新本地的最新的音讯 ID,而后进行 ack 回包。服务端收到 ack 后会更新群成员的最新的音讯 ID。
6)离线成员:发送手机告诉栏告诉。离线成员上线后,拿本地最新的音讯 ID,去服务端拉取群 G 发送给 A 的音讯,通过 channel 通道来进行分页拉取,每一次申请,会将上一次拉取音讯的最大的 ID,作为申请参数来拉取音讯,这里相当于第二次拉取申请包是作为第一次拉取的 ack 包。
7)分页的状况下,客户端在收到上一页申请的的数据后更新本地的最新的音讯 ID 后,再申请下一页并且带上音讯 ID。上一页申请的的数据能够当作为 ack 来返回服务端,防止网络屡次交互。服务端收到 ack 后会更新群成员的最新的音讯 ID。

21、设计 IM 零碎时的常见疑难

21.1 相比传统 HTTP 申请的业务零碎,IM 业务零碎的有哪些不一样的设计难点?

次要是在线状态保护。相比于 HTTP 申请的业务零碎,接入层有状态,必须维持心跳和会话状态,加大了零碎设计复杂度。申请通信模型不一样。相比于 HTTP 申请一个 request 期待一个 response 通信模型,IM 零碎则是一个数据包在全双工长连贯通道双传输,客户端和服务端音讯交互的信令数据包设计简单。

21.2 对于单聊和群聊的实时性音讯,是否须要 MQ 来作为通信的中间件来代替 rpc?

MQ 作为解耦能够有以下益处:
1)易扩大:gate 层到 logic 层无需路由,logic 层多个有新的业务时候,只须要监听新的 topic 即可;
2)解耦:gate 层到 logic 层解耦,不会有依赖关系;
3)节俭端口资源:gate 层无需再开启新的端口接管 logic 的申请,而且间接监听 MQ 音讯即可。
然而毛病也有:
1)网络通信多一次网络通信,减少 RT 的工夫,音讯实时性对于 IM 即便通信的场景是十分重视的一个点;
2)MQ 的稳定性,不论任何零碎只有引入中间件都会有稳定性问题,须要思考 MQ 不可用或者失落数据的状况;
3)须要思考到运维的老本;
4)当用音讯两头代替路由层的时候,gate 层须要播送生产音讯,这个时候 gate 层会接管大部分的有效音讯,因为这个音讯的接收者 channel 不在本机保护的 session 中。
综上:是否思考应用 MQ 须要架构师去考量,比方思考业务是否容许、或者零碎的流量、或者高可用设计等等影响因素。本我的项目基于应用老本、耦合老本和运维老本思考,采纳 Netty 作为底层自定义通信计划来实现,也能同样实现层级调用。
参考资料:
《阿里 IM 技术分享(九):深度揭密 RocketMQ 在钉钉 IM 零碎中的利用实际》。

21.3 为什么接入层用 LSB 返回的 IP 来做接入呢?

能够有以下益处:
1)灵便的负载平衡策略 可依据起码连接数来调配 IP;
2)做灰度策略来调配 IP;
3)AppId 业务隔离策略 不同业务连贯不同的 gate,避免相互影响。

21.4 为什么应用层心跳对连贯进行健康检查?

因为 TCP Keepalive 状态无奈反馈应用层状态问题,如过程阻塞、死锁、TCP 缓冲区满等状况。并且要留神心跳的频率,频率小则可能及时感知不到利用状况,频率大可能有肯定的性能开销。
参考资料:
《为何基于 TCP 协定的挪动端 IM 依然须要心跳保活机制?》、
《彻底搞懂 TCP 协定层的 KeepAlive 保活机制》。

21.5MQ 的应用场景?

IM 音讯是十分宏大的,比如说群聊相干业务、推送,对于一些业务上能够忍耐的场景,尽量应用 MQ 来解耦和通信,来升高同步通信的服务器压力。

21.6 群音讯存一份还是多份,读扩散还是写扩散?

我的设计是存 1 份,读扩散。存多份的话(也就是写扩散)下同一条音讯存储了很屡次,对磁盘和带宽造成了很大的节约。能够在架构上和业务上进行优化,来实现读扩散。当然,对于 IM 是应用读扩散还是写扩散来实现,这须要依据 IM 产品的业务定位来决定。比方微信就是写扩散(详见《企业微信的 IM 架构设计揭秘:音讯模型、万人群、已读回执、音讯撤回等》),而钉钉却是读扩散(详见《深度解密钉钉即时消息服务 DTIM 的技术设计》)。

21.7 音讯 ID 为什么是趋势递增就能够,严格递增的不行吗?

严格递增会有单点性能瓶颈,比方 MySQL auto increments。redis 性能好然而没有业务语义,比方短少工夫因素,还可能会有数据失落的危险,并且集群环境下写入 ID 也属于单点,属于集中式生成服务。小型 IM 能够依据业务场景需要间接应用 redis 的 incr 命令来实现 IM 音讯惟一 ID。本我的项目采纳 snowflake 算法实现惟一趋势递增 ID,即可实现 IM 音讯中,时序性,重复性以及查找性能。
对于音讯 ID 的生成,能够参考上面的系列文章:
《微信的海量 IM 聊天音讯序列号生成实际(算法原理篇)》
《微信的海量 IM 聊天音讯序列号生成实际(容灾计划篇)》
《解密融云 IM 产品的聊天音讯 ID 生成策略》
《深度解密美团的分布式 ID 生成算法》
《开源分布式 ID 生成器 UidGenerator 的技术实现》
《深度解密滴滴的高性能 ID 生成器(Tinyid)》

21.8gate 层为什么须要开两个端口?

gate 会接管客户端的连贯申请(被动),须要外网监听端口;entry 会被动给 logic 发申请(被动);entry 会接管服务端给它的告诉申请(被动),须要内网监听端口。一个端口对内,一个端口对外。

21.9 用户的路由信息,是保护在地方存储的 redis 中,还是保护在每个 msg 层内存中?

保护在每个 msg 层内存中有状态:多级缓存防止和中间件屡次交互,并发高。保护在地方存储的 redis 中,msg 层无状态,redis 压力大,每次交互 IO 网络申请大。业务初期为了缩小复杂度,能够保护在 Redis 中。

21.10 网关层和服务层以及 msg 层和网关层申请模型具体是怎么的?

网关层到服务层,只须要单向传输发申请,网关层不须要关怀调用的后果。而客户端想要的 ack 或者 notify 申请是由 SDK 发送数据到网关层,SDK 也不须要关怀调用的后果,最初网关层只转发数据,不做额定的逻辑解决。SDK 和所有的网关进行长连贯,当发送信息给客户端时,依据路由寻址信息,即可通过长连贯推送信息

21.11 本地写数据胜利,肯定代表对端利用侧接管读取音讯了吗?

本地 TCP 写操作胜利,但数据可能还在本地写缓冲区中、网络链路设施中、对端读缓冲区中,并不代表对端利用读取到了数据。如果你还不了解,能够读读这篇文章《从客户端的角度来谈谈挪动端 IM 的音讯可靠性和送达机制》。

21.12 为什么用 netty 做来做 http 网关, 而不必 tomcat?

次要是从以下方面思考:
1)netty 对象池,内存池,高性能线程模型;
2)netty 堆外内存治理,缩小 GC 压力,jvm 治理的只是一个很小的 DirectByteBuffer 对象援用;3)tomcat 读取数据和写入数据都须要从内核态缓冲 copy 到用户态的 JVM 中,多 1 次或者 2 次的拷贝会有性能影响。

21.13 为什么音讯入库后,对于在线状态的用户,单聊间接推送,群聊告诉客户端来拉取,而不是间接推送音讯给客户端(推拉联合)?

在保障音讯实时性的前提下,对于单聊,间接推送。对于群聊,因为群聊人数多,推送的话一份群音讯会对群内所有的用户都产生一份推送的音讯,推送量微小。解决办法是按需拉取,当群音讯有新音讯时候发送时候,服务端被动推送新的音讯数量,而后客户端分页按需拉取数据。

21.14 为什么除了单聊、群聊、推送、离线拉取等实时性业务,其余的业务都走 http 协定?

IM 协定简略最好,如果让其余的业务申请混进 IM 协定中,会让其 IM 变的更简单,比方查找离线音讯记录拉取走 http 通道防止 tcp 通道压力过大,影响即时消息下发效率。在比方上传图片和大文件,能够利用 HTTP 的断点上传和分段上传个性。

21.15 机集群机器要思考到哪些优化?

次要有:
1)网络宽带;
2)最大文件句柄;
3)每个 tcp 的内存占用;
4)Linux 零碎内核 tcp 参数优化配置;
5)网络 IO 模型;
6)网络网络协议解析效率;
7)心跳频率;
8)会话数据一致性保障;
9)服务集群动静扩容缩容。

22、系列文章

《跟着源码学 IM(一):手把手教你用 Netty 实现心跳机制、断线重连机制》
《跟着源码学 IM(二):自已开发 IM 很难?手把手教你撸一个 Andriod 版 IM》
《跟着源码学 IM(三):基于 Netty,从零开发一个 IM 服务端》
《跟着源码学 IM(四):拿起键盘就是干,教你徒手开发一套分布式 IM 零碎》
《跟着源码学 IM(五):正确理解 IM 长连贯、心跳及重连机制,并入手实现》
《跟着源码学 IM(六):手把手教你用 Go 疾速搭建高性能、可扩大的 IM 零碎》
《跟着源码学 IM(七):手把手教你用 WebSocket 打造 Web 端 IM 聊天》
《跟着源码学 IM(八):万字长文,手把手教你用 Netty 打造 IM 聊天》
《跟着源码学 IM(九):基于 Netty 实现一套分布式 IM 零碎》
《跟着源码学 IM(十):基于 Netty,搭建高性能 IM 集群(含技术思路 + 源码)》
《跟着源码学 IM(十一):一套基于 Netty 的分布式高可用 IM 具体设计与实现(有源码)》(* 本文)《SpringBoot 集成开源 IM 框架 MobileIMSDK,实现即时通讯 IM 聊天性能》

23、参考资料

[1] 史上最艰深 Netty 框架入门长文:根本介绍、环境搭建、入手实战
[2] 强列倡议将 Protobuf 作为你的即时通讯利用数据传输格局
[3] IM 通信协定专题学习(一):Protobuf 从入门到精通,一篇就够!
[4] 微信新一代通信安全解决方案:基于 TLS1.3 的 MMTLS 详解
[5] 探讨组合加密算法在 IM 中的利用
[6] 从客户端的角度来谈谈挪动端 IM 的音讯可靠性和送达机制
[7] IM 音讯送达保障机制实现(一):保障在线实时音讯的牢靠投递
[8] 了解 IM 音讯“可靠性”和“一致性”问题,以及解决方案探讨
[9] 融云技术分享:全面揭秘亿级 IM 音讯的牢靠投递机制
[10] IM 群聊音讯如此简单,如何保障不丢不重?
[11] 零根底 IM 开发入门(四):什么是 IM 零碎的音讯时序一致性?
[12] 一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等
[13] 如何保障 IM 实时音讯的“时序性”与“一致性”?
[14] 阿里 IM 技术分享(六):闲鱼亿级 IM 音讯零碎的离线推送达到率优化
[15] 微信的海量 IM 聊天音讯序列号生成实际(算法原理篇)
[16] 社交软件红包技术解密(一):全面解密 QQ 红包技术计划——架构、技术实现等
[17] 网易云信技术分享:IM 中的万人群聊技术计划实际总结
[18] 企业微信的 IM 架构设计揭秘:音讯模型、万人群、已读回执、音讯撤回等
[19] 融云 IM 技术分享:万人群聊音讯投递计划的思考和实际
[20] 为何基于 TCP 协定的挪动端 IM 依然须要心跳保活机制?
[21] 一文读懂即时通讯利用中的网络心跳包机制:作用、原理、实现思路等
[22] 微信团队原创分享:Android 版微信后盾保活实战分享(网络保活篇)
[23] 融云技术分享:融云安卓端 IM 产品的网络链路保活技术实际
[24] 阿里 IM 技术分享(九):深度揭密 RocketMQ 在钉钉 IM 零碎中的利用实际
[25] 彻底搞懂 TCP 协定层的 KeepAlive 保活机制
[26] 深度解密钉钉即时消息服务 DTIM 的技术设计

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

正文完
 0