浅谈 IM SDK 的设计

IM SDK

Posted by CMB on July 31, 2017

由于公司向社区化转型,所以对 IM 的需求开始加大了,IM SDK 开发迫在眉睫,以下就浅谈一下本人在写 IM SDK 时的一些心得和见解,大神们勿喷,哈哈😆

IM 总体结构:

通用 IM 将用户信息、好友关系、群组关系这些强业务相关数据留给业务方来维护,通用 IM 不对这些数据做存储和同步。

其实这个结构是后端设计的,我觉得发送和接收都应该走长连接的,唉😔

消息上行:

上行消息通过 App Server 处理后,再由业务 App Server 调用 IM Server 的服务端接口,将上行消息发送出去;由 App Server 来进行关系链、用户封禁、禁言等的业务权限控制检查。

消息下行:

下行消息通过 IM Server 来进行推送,若接收方在线,则通过保持的长连接将消息推送给在线客户端;若接收方不在线,则根据是否需要离线推送提醒由 IM Server 调用统一推送系统进行离线 Push 通知。

通讯协议:

针对移动互联网的特点:带宽低、延迟高、丢包率高、稳定性差、流量费用高, 所以在IM私有协议的序列化上一般使用二进制协议,而不是文本协议。IM SDK 则采用Protocol Buffer 数据格式 + Gzip 压缩方案。

SDK 结构:

老司机俯视角度 🚘 :

解剖一下 🔪 :

断线重连策略:

理想情况下,客户端应在 IM 通道断掉时立即尝试重连,这样能保证用户聊天体验的中断被最小化,但实际上,如果有大量客户端在因为网络波动同一时间重连服务器,可能造成服务端瞬间被撑爆。因此客户端在断线后需遵循一定的重连策略。

当前的重连策略为:

  1. 每次重连时间点随机,这样规避了瞬间大流量的请求;
  2. 参考 iOS 系统 TCP 超时重传的积极策略,以适应移动网络的不稳定特征;
  3. 定义了若干个重连的级别,第一次重连在 (0-1] 秒随机,第二次重连 (1-2] 秒随机,第三次 (2-4] 秒随机,第四次 (4,16] ,最大是达到 (16-32]

其实就这个级别: (0-1]、(1-2]、(2-4]、(4-8]、(8-16]、(16-32] 🤠

心跳包策略:

长连接保活

TCP提供的 KeepAlive 机制

TCP有个KeepAlive开关,打开后可以用来检测死连接。通常默认是 2 小时,支持用户设置,但KeepAlive是TCP的全局设置。假如为了能更及时的检测出断开的连接,把 tcp_keepalive_timetcp_keepalive_intvl 的时间改小(参考:Using TCP keepalive under Linux ),该机器上所有应用程序的 KeepAlive 检测间隔都会变小,显然是不能接受的。因为不同应用程序的需求是不一样的。最主要的原因是部分复杂情况下 KeepAlive 会失效,如路由器挂掉,网络直接被拔除。

在某些平台的 Socket 实现已经支持为每条连接单独设置 KeepAlive 参数

KeepAlive 本质上来说,是用来检测长时间不活跃的连接的。所以,不适合用来及时检测连接的状态。

心跳包( HeartBeat

心跳包就是用来及时检测是否断线以及维持客户端和服务端长连接有效性的一种机制,通过每间隔一定时间发送心跳数据,来检测套接字是否有效。是属于应用程序协议的一部分。

心跳包为什么是好的方式及时检测连接状态?

  1. 灵活性更高,可以自己控制检测的间隔,检测的方式等等;
  2. 有些情况下,心跳包可以附带一些其他信息,定时在服务端和客户端之间同步(比如服务端时间同步);
  3. 上面说到心跳包是每间隔一定时间发送,必然会消耗一定的流量和电量,所以心跳包时间间隔的设定、或者对心跳包的机制优化也是非常重要的。

各大 IM 平台的心跳值(挖空它们的心😈)

  微信 手Q 企微 WhatsApp Line 闪聊
WIFI 固定4分30秒,后台动态 固定2分30秒,后台动态 固定20秒 固定4分45秒 固定3分20秒 固定3分30秒
手机网络 固定4分30秒,后台动态 固定2分30秒,后台动态 固定20秒 固定4分45秒 固定7分钟 固定3分30秒

影响 TCP 连接寿命的因素

1. NAT 超时;

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断(NAT超时的更多描述见下 Session )。

NAT 超时是影响 TCP 连接寿命的一个重要因素(尤其是国内),所以客户端自动测算 NAT 超时时间,来动态调整心跳间隔,是一个重要的优化点。

2. DHCP的租期( lease time );
3. 网络状态变化。

手机网络和WIFI网络切换、网络断开和连上等情况有网络状态的变化,也会使长连接变为无效连接,需要监听响应的网络状态变化事件,重新建立 Push 长连接。

NAT超时

问:什么是NAT?🤔

答:因为 IPv4IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP ,手机要连接 Internet ,就需要通过运营商的网关做一个网络地址转换( Network Address Translation,简称:NAT )。

简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。

NAT图(网上随便找的 LOW 图,懒得画了):

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断。

下表列出一些已测试过的网络的 NAT 超时时间(更多数据由于测试条件所限没有测到):

地区/网络 NAT超时时间
中国移动 4G 1分钟
中国移动 3G、2G 5分钟
中国联通 4G、3G、2G 5分钟
中国电信 4G 5分钟
中国电信 3G、2G 大于28分钟
台湾和香港各大运营商 4G、3G 大于28分钟
美国各大运营商 4G、3G 大于28分钟

长连接心跳间隔必须要小于 NAT 超时时间( aging-time ),如果超过 NAT 超时时间不做心跳,TCP 长连接链路就会中断,Server 就无法发送 Push 给手机,只能等到客户端下次心跳失败后,重建连接才能取到消息。

心跳范围选择

1. 前后台区分处理:

为了保证 IM SDK 收消息及时性的体验,当 IM SDK 处于前台活跃状态时,使用固定心跳。

IM SDK 进入后台(或者前台关屏)时,先用几次最小心跳维持长链接,然后进入后台自适应心跳计算。这样做的目的是尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。

2. 后台自适应心跳选择区间:

可根据自身产品的特点选择合适的心跳范围。

动态心跳算法描述

1. 按网络类型区分计算:

因为每个网络的NAT时间可能不一致。所以需要区分计算,数据网络按 type 做关键字,WIFIWIFI 名做关键字。

2. 一次肯定,多次否定:

因为失败有可能是暂态的,所以设置多次失败才算真正意义上的失败,但是成功是一次肯定。

大致流程

a) 变量说明:

[MinHeart,MaxHeart] —— 心跳可选区间
currentHeart —— 当前心跳初始值为 successHeart
heartStep —— 心跳增加步长,值为 MaxHeart10%
failedTimes —— 当前失败次数

b) 步骤:

动态心跳计算流程如图所示,第一次心跳为最大心跳的 50%,每次成功步长添加最大心跳的 10%,每次失败都按照当前心跳减去每次步长心跳(最大心跳的 10%)的 3 倍,这样能确保快速趋近于最小心跳值,当失败 3 次后就判断为真正的失败,将启动断线重连策略,如果一次成功就会将失败次数清空,按照 一次成功,多次失败 的策略。(上面所说的参数都是可以项目自行定制,不一定为 3 次,只是我们做了研究三次是用户体验比较好的值👻)

举例:

场景:

假设当前在移动 4G 环境

参数:

NAT超时为(NAT): 60
最大心跳为(MaxHeart):60 * 90% = 54
最小心跳为(MinHeart):60 * 10% = 6
每次步长(heartStep):60 * 10% = 6
开始心跳:NAT * 50% = 30

步骤:

成功一次:30 + 6 = 36
失败一次:30 - 6 * 3 = 12

后期优化(懒惰没完成的借口)

1. 冗余 Sync 和心跳

在用户的一些主动操作以及联网状态改变时,增加冗余 Sync 和心跳,确保及时收到消息。

  1. 当用户点亮屏幕的时候,做一次心跳;
  2. 当切换到前台时,做一次 Sync
  3. 联网时重建信令 TCP ,做一次 Sync

2. 消息存储

排一下期怼进去吧 : )

3. 消息去重

用户在某条发送失败的消息点击重发、或者断线重连时对还处于发送中状态的消息自动重发时,需设置 PB 消息体中的 isResend 标志位为 true,同时透传这条重发消息的 local id。当服务端发现 isResend 被标记,就会启动滤重,即对相同 local id 的消息不再进行路由,而是直接返回给发送方一个 sent ack

4. 动态计算 NAT 超时

由于 NAT 超时是动态变化的,我们可以用最大值探测,动态查找最小的失败值,更加真实逼近NAT 超时时间。

参考文献

  1. Android微信智能心跳方案
  2. 闲说HeartBeat心跳包和TCP协议的KeepAlive机制
  3. 闪聊一些技术点整理
  4. 环信SDK文档
  5. LeanCloud 实时通讯SDK文档
  6. 融云开发文档

好吧,先说这么多吧🙄,hoho~!!
感谢 Sai(嘉蓉) 帮忙画的图