关于即时通讯:探探的IM长连接技术实践技术选型架构设计性能优化

90次阅读

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

本文由探探服务端高级技术专家张凯宏分享,原题“探探长链接我的项目的 Go 语言实际”,因原文内容有较多谬误,有订正和改变。

1、引言

即时通信长连贯服务处于网络接入层,这个畛域非常适合用 Go 语言施展其多协程并行、异步 IO 的特点。

探探自长连贯我的项目上线当前,对服务进行了屡次优化:GC 从 5ms 降到 100 微秒(Go 版本均为 1.9 以上),次要 gRPC 接口调用延时 p999 从 300ms 降落到 5ms。在业内大多把眼光聚焦于单机连接数的时候,咱们则更聚焦于服务的 SLA(服务可用性)。

本文将要分享的是陌生人社交利用探探的 IM 长连贯模块从技术选型到架构设计,再到性能优化的整个技术实际过程和经验总结。

学习交换:

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

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

2、对于作者

张凯宏:负责探探服务端高级技术专家。

6 年 Go 语言开发教训,曾用 Go 语言构建多个大型 Web 我的项目,其中波及网络库、存储服务、长连贯服务等。专一于 Go 语言实际、存储服务研发及大数据场景下的 Go 语言深度优化。

3、我的项目缘起

咱们这个我的项目是 2018 年下半年开始,据明天大略 1 年半工夫。

过后探探遇到一些技术痛点,最重大的就是重大依赖第三方 Push,比如说第三方有一些故障的话,对实时 IM 聊天的 KPS 有比拟大的影响。

过后通过 push 推送音讯,利用内的 push 延时比拟高,均匀延时五六百毫秒,这个工夫咱们不能承受。

而且也没有一个 Ping Pland 机制(心跳查看机制?),无奈晓得用户是否在线。

过后产品和技术同学都感觉是机会搞一个长连贯了。

4、一个小插曲

我的项目大略继续了一个季度工夫,首先是拿 IM 业务落地,咱们感觉长连贯跟 IM 绑定比拟严密一些。

IM 落地之后,后续长连贯上线之后,各个业务比拟依赖于长连贯服务。

这两头有一个小插曲,次要是取名字那一块。

我的项目之初给我的项目起名字叫 Socket,看到 socket 比拟亲切,感觉它就是一个长连贯,这个感觉比拟莫名,不晓得为什么。但运维提出了异议,感觉 UDP 也是 Socket,我感觉 UDP 其实也能够做长连贯。

运维提议叫 Keepcom,这个是出自于 Keep Alive 实现的,这个提议还是挺不错的,最初咱们也是用了这个名字。

客户端给的倡议是 Longlink,另外一个是 Longconn,一个是 IOS 端技术共事取的、一个是安卓端技术共事取的。

最初咱们都败了,运维同学胜了,运维同学感觉,如果名字定不下来就别上线的,最初咱们斗争了。

5、为什么要做长连贯?

为什么做长连贯?

如上图所示:看一下比照挺显著,右边是长连贯,左边是短长连贯。

对于长连贯来说,不须要从新进入连贯,或者是开释连贯,一个 X 包只须要一个 RTT 就完事。左边对于一个短连贯须要三次握手发送一个 push 包,最初做挥手。

论断:如果发送 N 条音讯的数据包,对于长连贯是 2 + N 次的 RTT,对于短连贯是 3N 次 RTT,最初开启 Keep Alive,N 是连贯的个数。

6、长连贯技术劣势

咱们决结了一下,长连贯有以下四大劣势:

1)实时性:长连贯是双向的通道,对音讯的推送也是比拟实时;
2)有状态:长连贯自身保护用户的状态,通过 KeepAlive 形式,确定用户是否在线;
3)省流程:长连贯比拟省流量,能够做一些用户自定义的数据压缩,自身也能够省不少的归属包和连贯包,所以说比拟省流量;
4)更省电:缩小网络流量之后,可能进一步升高挪动客户端的耗电。

7、TCP 在挪动端能胜任吗?

在我的项目开始之前,咱们做了比拟多的考量。

首先咱们看一下对于挪动端的长连贯来说,TCP 协定是不是可能 Work?

对于传统的长连贯来说,Web 端的长连贯 TCP 能够胜任,在挪动端来说 TCP 是否胜任?这取决于 TCP 的几个个性。

首先 TCP 有慢启动和滑动窗口的个性,TCP 通过这种形式管制 PU 包,防止网络阻塞。

TCP 连贯之后走一个慢启动流程,这个流程从初始窗大小做 2 个 N 次方的扩张,最初到肯定的域值,比方域值是 16 包,从 16 包开始逐渐往上递增,最初到 24 个数据包,这样达到窗口最大值。

一旦遇到丢包的状况,当然两种状况。一种是疾速重传,窗口简略了,相当于是 12 个包的窗口。如果启动一个 RTO 相似于状态连贯,窗口一上涨到初始的窗口大小。

如果启动 RTO 重传的话,对于后续包的阻塞蛮重大,一个包阻塞其余包的发送。


(▲ 上图援用自《迈向高阶:优良 Android 程序员必知必会的网络根底》)

无关 TCP 协定的基础知识,能够读读以下材料:

《TCP/IP 详解 – 第 17 章·TCP:传输控制协议》
《TCP/IP 详解 – 第 18 章·TCP 连贯的建设与终止》
《TCP/IP 详解 – 第 21 章·TCP 的超时与重传》
《通俗易懂 - 深刻了解 TCP 协定(上):实践根底》
《通俗易懂 - 深刻了解 TCP 协定(下):RTT、滑动窗口、拥塞解决》
《网络编程懒人入门(一):疾速了解网络通信协定(上篇)》
《网络编程懒人入门(二):疾速了解网络通信协定(下篇)》
《网络编程懒人入门(三):疾速了解 TCP 协定一篇就够》
《脑残式网络编程入门(一):跟着动画来学 TCP 三次握手和四次挥手》
《网络编程入门从未如此简略(二):如果你来设计 TCP 协定,会怎么做?》

8、TCP 还是 UDP?

(▲ 上图援用自《挪动端 IM/ 推送零碎的协定选型:UDP 还是 TCP?》)

TCP 实现长连贯的四个问题:

1)挪动端的音讯量还是比拟稠密,用户每次拿到手机之后,发的音讯总数比拟少,每条音讯的距离比拟长。这种状况下 TCP 的间连和放弃长链接的劣势比拟显著一些;
2)弱网条件下丢包率比拟高,丢包后 Block 后续数据发送容易阻塞;
3)TCP 连贯超时工夫过长,默认 1 秒钟,这个因为 TCP 诞生的年代比拟早,那会儿网络状态没有当初好,过后定是 1s 的超时,当初能够设的更短一点;
4)在没有疾速重传的状况下,RTO 重传等待时间较长,默认 15 分钟,每次是 N 次方的递加。

为何最终还是抉择 TCP 呢?因为咱们感觉 UDP 更重大一点。

首先 UDP 没有滑动窗口,无流量管制,也没有慢启动的过程,很容易导致丢包,也很容易导致在网络中间状态下丢包和超时。

UDP 一旦丢包之后没有重传机制的,所以咱们须要在应用层去实现一个重传机制,这个开发量不是那么大,然而我感觉因为比拟偏底层,容易出故障,所以最终抉择了 TCP。

TCP 还是 UDP?这始终是个比拟有争议的话题:

《网络编程懒人入门(四):疾速了解 TCP 和 UDP 的差别》
《网络编程懒人入门(五):疾速了解为什么说 UDP 有时比 TCP 更有劣势》
《5G 时代曾经到来,TCP/IP 老矣,尚能饭否?》
《Android 程序员必知必会的网络通信传输层协定——UDP 和 TCP》
《鲜为人知的网络编程(六):深刻地了解 UDP 协定并用好它》
《鲜为人知的网络编程(七):如何让不牢靠的 UDP 变的牢靠?》

如果你对 UDP 协定还不理解,能够读读这篇:《TCP/IP 详解 – 第 11 章·UDP:用户数据报协定》。

9、抉择 TCP 的更多理由

咱们列举一下,次要有这 3 点:

1)目前在挪动端、安卓、IOS 来说,初始窗口大小比拟大默认是 10,综合 TCP 慢启动的劣势来看;
2)在一般的文本传输状况下,对于丢包的重大不是很敏感(并不是说传多媒体的数据流,只是传一些文本数据,这一块对于丢包的副作用 TCP 不是特地重大);
3)咱们感觉 TCP 在应用层用的比拟多。

对于第“3)”点,这里有以下三个考量点。

第一个考量点:

根本当初应用程序走 HTP 协定或者是 push 形式根本都是 TCP,咱们感觉 TCP 个别不会出大的问题。

一旦摈弃 TCP 用 UDP 或者是 QUIC 协定的话,保不齐会呈现比拟大的问题,短时间解决不了,所以最终用了 TCP。

第二个考量点:

咱们的服务在根底层上用哪种形式做 LB,过后有两种抉择,一种是传统的 LVS,另一种是 HttpDNS(对于 HttpDNS 请见《全面理解挪动端 DNS 域名劫持等杂症:原理、本源、HttpDNS 解决方案等》)。

最初咱们抉择了 HttpDNS,首先咱们还是须要跨机房的 LB 反对,这一点 HttpDNS 齐全胜出。其次,如果须要跨网端的话,LVS 做不到,须要其余的部署形式。再者,在扩容方面,LVS 算是稍逊一筹。最初,对于个别的 LB 算法,LVS 反对并不好,须要依据用户 ID 的 LB 算法,另外须要一致性哈希的 LB 算法,还须要依据地理位置的定位信息,在这些方面 HttpDNS 都可能完满的胜出,然而 LVS 都做不到。

第三个考量点:

咱们在做 TCP 的饱和机制时通过什么样的形式?Ping 包的形式,间隔时间怎么确定,Ping 包的工夫细节怎么样确定?

过后比拟纠结是客户端被动发 ping 还是服务端被动发 Ping?

对于客户端保活的机制反对更好一些,因为客户端可能会被唤醒,然而客户端进入后盾之后可能发不了包。

其次:APP 前后台对于不同的 Ping 包距离来保活,因为在后盾自身处于一种弱在线的状态,并不需要去频繁的发 Ping 包确定在线状态。

所以:在后盾的 Ping 包的工夫距离能够长一些,前端能够短一些。

再者:须要 Ping 指数增长的距离反对,在故障的时候还是比拟救命的。

比如说:服务端一旦故障之后,客户端如果拼命 Ping 的话,可能把服务端彻底搞瘫痪了。如果有一个指数级增长的 Ping 包距离,根本服务端还能缓一缓,这个在故障时比拟重要。

最初:Ping 包重试是否须要 Backoff,Ping 包从新发 Ping,如果没有收到 Bang 包的话,须要等到 Backoff 发 Ping。

10、动静 Ping 包工夫距离算法

PS:在 IM 里这其实有个更业余的叫法——“智能心跳算法”。

咱们还设计了一个动静的 Ping 包工夫距离算法。

因为国内的网络运营商对于 NIT 设施有一个保活机制,目前根本在 5 分钟以上,5 分钟如果不发包的话,会把你的缓存给删掉。基本上各运营商都在 5 分钟以上,只不过挪动 4G 妨碍了。根本能够在 4 到 10 分钟之内发一个 Ping 包就行,能够维持网络运营商设施里的缓存,始终放弃着,这样就没有问题,使长连贯始终保活着。

减少 Ping 包距离能够缩小网络流量,可能进一步升高客户端的耗电,这一块的受害还是比拟大的。

在低端安卓设施的状况下,有一些 DHCP 租期的问题。这个问题集中在安卓端的低版本上,安卓不会去续租过期的 IP。

解决问题也比较简单,在 DHCP 租期到一半的时候,去及时向 DHCP 服务器续租一下就能解决了。

限于篇幅,我就不在这里开展了,有趣味能够读这些材料:

《为何基于 TCP 协定的挪动端 IM 依然须要心跳保活机制?》
《一文读懂即时通讯利用中的网络心跳包机制:作用、原理、实现思路等》
《微信团队原创分享:Android 版微信后盾保活实战分享(网络保活篇)》
《挪动端 IM 实际:实现 Android 版微信的智能心跳机制》
《挪动端 IM 实际:WhatsApp、Line、微信的心跳策略剖析》
《一种 Android 端 IM 智能心跳算法的设计与实现探讨(含样例代码)》
《手把手教你用 Netty 实现网络通信程序的心跳机制、断线重连机制》

11、服务架构

11.1 根本介绍
服务架构比较简单,大略是四个模块:

1)首先是 HttpDNS;
2)另一个是 Connector 接入层,接入层提供 IP,
3)而后是 Router,相似于代理转发音讯,依据 IP 抉择接入层的服务器,最初推到用户;
4)最初还有认证的模块 Account,咱们目前只是探探 APP,这个在用户核心实现。

11.2 部署
部署上相当于三个模块:

1)一个是 Dispatcher;
2)一个是 Redis;
3)一个是 Cluser。

如下图所示:客户端在连贯的时候:

1)须要拿到一个协定;
2)第二步通过 HttpDNS 拿到 ConnectorIP;
3)通过 IP 连长连贯,下一步发送 Auth 音讯认证;
4)连贯胜利,前面发送 Ping 包保活;
5)之后断开连接。

11.3 音讯转发流程
音讯转发的流程分为两个局部。

首先是音讯上行:服务端发动一个音讯包,通过 Connector 接入服务,客户端通过 Connector 发送音讯,再通过 Connector 把音讯发到微服务上,如果不须要微服务的话间接去转发到 Vetor 就行的,这种状况下 Connector 更像一个 Gateway。

对于上行:业务方都须要申请 Router,找到具体的 Connector,依据 Connector 部署音讯。

各个公司都是微服务的架构,长连贯跟微服务的交互根本两块。一块是音讯上行时,更像是 Gateway,上行通过 Router 接入,通过 Connector 发送音讯。

11.4 一些实现细节
上面是一些是细节,咱们用了 GO 语言 1.13.4,内部消息传输上是 gRPC,传输协定是 Http2,咱们在外部通过 ETCD 做 LB 的形式,提供服务注册和发现的服务。

如下图所示:Connector 就是状态,它从用户 ID 到连贯的一个状态信息。

咱们看下图的左边:它其实是存在一个比拟大的 MAP,为了避免 MAP 的锁竞争过于重大,把 MAP 拆到 2 到 56 个子 MAP,通过这种形式去实现高读写的 MAP。对于每一个 MAP 从一个 ID 到连贯状态的映射关系,每一个连贯是一个 Go Ping,实现细节读写是 4KB,这个没改过。

咱们看一下 Router:它是一个无状态的 CommonGRPC 服务,它比拟容易扩容,当初状态信息都存在 Redis 外面,Redis 大略一组一层,目前峰值是 3000。

咱们有两个状态:一个是 Connector,一个是 Router。

首先以 Connector 状态为主,Router 是状态统一的保障。

这个外面分为两种状况:如果连贯在同一个 Connector 上的话,Connector 须要保障向 Router 复制的程序是正确的,如果程序不统一,会导致 Router 和 Connector 状态不统一。通过对立 Connector 的窗口实现音讯一致性,如果跨 Connector 的话,通过在 Redis Lua 脚本实现 Compare And Update 形式,去保障只有本人 Connector 写的状态能力被本人更新,如果是别的 Connector 的话,更新不了其他人的信念。咱们保障跨 Connector 和同一 Connector 都可能去依照程序通过统一的形式更新 Router 外面连贯的状态。

Dispatche 比较简单:是一个纯正的 Common Http API 服务,它提供 Http API,目前延时比拟低大略 20 微秒,4 个 CPU 就能够撑持 10 万个并发。

目前通过无单点的构造实现一个高可用:首先是 Http DNS 和 Router,这两个是无障碍的服务,只须要通过 LB 保障。对于 Connector 来说,通过 Http DNS 的客户端被动漂移实现连贯层的 Ordfrev,通过这种形式保障一旦一个 Connector 出问题了,客户端能够立马漂到下一个 Connector,去实现主动的工作转移,目前是没有单点的。

12、性能优化

12.1 根本状况
后续有优化次要有以下几个方面:

1)网络优化:这一块拉着客户端一起做,首先客户端须要重传包的时候发三个嗅探包,通过这种形式做一个疾速重传的机制,通过这种机制进步疾速重传的比例;
2)心跳优化:通过动静的 Ping 包间隔时间,缩小 Ping 包的数量,这个还在开发中;
3)避免劫持:是通过客户端应用 IP 直连形式,回避域名劫持的操作;
4)DNS 优化:是通过 HttpDNS 每次返回多个 IP 的形式,来申请客户端的 HttpDNS。
12.2 网络优化
对于接入层来说,其实 Connector 的连接数比拟多,并且 Connector 的负载也是比拟高。

咱们对于 Connector 做了比拟大的优化,首先看 Connector 最早的 GC 工夫到了 4、5 毫秒,惨不忍睹的。

咱们看一下上面这张图(图上)是优化后的后果,大略均匀 100 微秒,这算是比拟好。第二张图(图下)是第二次优化的后果,大略是 29 微秒,第三张图大略是 20 几微秒。

12.3 音讯提早
看一下音讯提早,探探对 im 音讯的提早要求比拟高,特地重视用户的体验。

这一块刚开始大略到 200ms,如果对于一个操作的话,200ms 还是比较严重的。

第一次优化之后(下图 - 上)的状态大略 1 点几毫秒,第二次优化之后(下图 - 下)当初降到最低点差不多 100 微秒,跟个别的 Net 操作工夫维度上比拟靠近。

12.4 Connector 优化过程
优化过程是这样的:

1)首先须要要害门路上的 Info 日志,通过采样实现 Access Log,info 日志是接入层比拟重的操作;
2)第二通过 Sync.Poll 缓存对象;
3)第三通过 Escape Analysis 对象尽可能在线上调配。
前面还实现了 Connector 的无损发版:这一块比拟有价值。长连贯刚上线发版比拟多,每次发版对于用户来说都有感,通过这种形式让用户尽量无感。

实现了 Connector 的 Graceful Shutdown 的形式,通过这种形式优化连贯。

首先:在 HttpDNS 高低线该机器,下线之后迟缓断开用户连贯,直到连接数小于肯定阈值。前面是重启服务,发版二进制。

最初:是 HttpDNS 上线该机器,通过这种形式实现用户发版,工夫比拟长,过后测了挺长时间,去掂量每秒钟断开多少个连贯,最初阈值是多少。

前面是一些数据:方才 GC 也是一部分,目前连接数都属于比拟要害的数据。首先看连接数单机连接数比拟少,不敢放太开,最多是 15 万的单机连接数,大概 100 微秒。

Goroutine 数量跟连接数一样,差不多 15 万个:

看一下内存应用状态,下图 (上) 是 GO 的内存总量,大略是 2:3,剩下五分之一是属于未占用,内存总量是 7.3 个 G。

下图是 GC 状态,GC 比拟衰弱,红线是 GC 每次沉闷内存数,红线远远高于绿线。

看到 GC 目前的情况大略是 20 几微秒,感觉目前跟 GO 的官网工夫比拟能对得上,咱们感觉 GC 目前都曾经优化到位了。

12.5 后续要做的优化
最初是布局后续还要做优化。

首先:对系统上还是须要更多优化 Connector 层,更多去缩小内存的调配,尽量把内存调配到堆上而不是站上,通过这种形式缩小 GC 压力,咱们看到 GO 是非 Generational Collection GE,堆的内存越多的话,扫的内存也会越多,这样它不是一个线性的增长。

第二:在外部更多去用 Sync Pool 做短暂的内存调配,比如说 Context 或者是长期的 Dbyle。

协定也要做优化:目前用的是 WebSocket 协定,前面会加一些性能标记,把一些重要信息传给服务端。比如说一些重传标记,如果客户端退出重传标记的话,咱们能够先校验这个包是不是重传包,如果是重传包的话会去判断这个包是不是反复,是不是之前发过,如果发过的话就不须要去解包,这样能够少做很多的服务端操作。

另外:能够去把 Websocket 目前的 Mask 机制去掉,因为 Mask 机制避免 Web 端的改包操作,然而根本是客户端的传包,所以并不需要 Mask 机制。

业务上:目前布局前面须要做比拟多的事件。咱们感觉长连贯因为是一个接入层,是一个十分好的中央去统计一些客户端的散布。比如说客户端的安卓、IOS 的散布情况。

进一步:能够做用户画像的统计,男的女的,年龄是多少,地理位置是多少。大略是这些,谢谢!

13、热门问题回复

  • 发问:方才说连贯层对话重启,间接的过程中那些断掉的用户就飘到其余的,是这样做的吗?

张凯宏:目前是这样的,客户端做主动飘移。

  • 发问:当初是 1 千万日活,如果服务端往客户端一下推 100 万,这种场景怎么做的?

张凯宏:目前咱们没有那么大的音讯推送量,有时候会发一些业务相干的推送,目前做了一个限流,通过客户端限流实现的,大略三四千。

  • 发问:如果做到后端,意味着会存在安全隐患,攻击者会不停的建设连贯,导致很难去做进攻,会有这个问题吗?因为歹意的攻打,如果攻打的话建设连贯就能够了,不须要认证的机制。

张凯宏:明确你的意思,这一块不只是长连贯,短连贯也有这个问题。客户端始终在伪造拜访后果,流量还是比拟大的,这一块靠防火墙和 IP 层防火墙实现。

  • 发问:长连贯服务器是挂在最外方,两头有没有一层?

张凯宏:目前接着如上层间接裸露在外网层,后面过一层 IP 的防 DNSFre 的防火墙。除此之外没有别的网络设备了。

  • 发问:基于什么样的思考两头没有加一层,因为后面还加了一层的状况。

张凯宏:目前没有这个打算,前面会在 Websofte 接入层后面加个 LS 层能够不便扩容,这个收益不是特地大,所以当初没有去打算。

  • 发问:刚刚说的断开重传的三次嗅探那个是什么意思?

张凯宏:咱们想更多的去触发疾速重传,这样对于 TCP 的重传距离更短一些,服务端依据三个循环包判断是否疾速重传,咱们会发三个循环包防止一个 RTO 重传的开启。

  • 发问:探探最开始安卓服务器是应用第三方的吗?

张凯宏:对的,刚开始是极光推送的。

  • 发问:从第三方的安卓服务器到自研。

张凯宏:如果极光有一些故障的话,对咱们影响还是蛮大。之前极光的故障频率挺高,咱们想是不是本人能把服务做起来。第二点,极光自身能提供一个用户是否在线的判断,然而它那个判断要走通道,延时比拟高,自身判断是连贯把延时升高一些。

  • 发问:比如说一个新用户上线连贯过去,有一些用户发给他音讯,他是怎么把一线音讯拿到的?

张凯宏:咱们通过业务端保障的,未收回来的音讯会存一个 ID 号,当用户从新连的时候,业务端再拉一下。

14、参考资料

[1] 挪动端 IM/ 推送零碎的协定选型:UDP 还是 TCP?
[2] 5G 时代曾经到来,TCP/IP 老矣,尚能饭否?
[3] 为何基于 TCP 协定的挪动端 IM 依然须要心跳保活机制?
[4] 一文读懂即时通讯利用中的网络心跳包机制:作用、原理、实现思路等
[5] 微信团队原创分享:Android 版微信后盾保活实战分享(网络保活篇)
[6] 挪动端 IM 实际:实现 Android 版微信的智能心跳机制
[7] 迈向高阶:优良 Android 程序员必知必会的网络根底
[8] 全面理解挪动端 DNS 域名劫持等杂症:原理、本源、HttpDNS 解决方案等
[9] 技术扫盲:新一代基于 UDP 的低延时网络传输层协定——QUIC 详解
[10] 新手入门一篇就够:从零开发挪动端 IM
[11] 长连贯网关技术专题(二):知乎千万级并发的高性能长连贯网关技术实际
[12] 长连贯网关技术专题(三):手淘亿级挪动端接入层网关的技术演进之路
[13] 长连贯网关技术专题(五):喜马拉雅自研亿级 API 网关技术实际
[14] 一套亿级用户的 IM 架构技术干货(上篇):整体架构、服务拆分等
[15] 一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等
[16] 从老手到专家:如何设计一套亿级音讯量的分布式 IM 零碎

本文已同步公布于“即时通讯技术圈”公众号。
同步公布链接是:http://www.52im.net/thread-37…

正文完
 0