本文由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)