长连接心跳原理与机制&&工程上踩坑与优化

QA: 业务上对于心跳间隔一般怎么确定?

       心跳间隔的确定一般需要根据具体业务场景和需求来进行。以下是一些常见的确定心跳间隔的方法:

  1. 根据应用场景和需求来确定心跳间隔。例如,在智能手环等健康监测设备中,心跳间隔通常设置为几秒钟到几分钟不等,以便及时监测用户的心率变化。

  2. 根据设备的硬件性能和网络带宽来确定心跳间隔。较低的硬件性能和网络带宽可能会导致心跳间隔需要设置得更长,以避免数据传输过于频繁而导致设备性能下降或者网络负载过大。

  3. 根据用户的使用习惯和偏好来确定心跳间隔。例如,在实时通信应用中,用户可能希望消息能够尽快地发送和接收,因此需要将心跳间隔设置得更短,以便及时检测网络连接状态。

总之,确定心跳间隔需要综合考虑多个因素,并根据具体业务场景和需求来进行调整。

背景

        心跳的目的很简单:通过定期的数据包,对抗NAT超时;维护链接可靠性。

Mars的心跳跟超时重传机制

        为什么要使用Mars

        通过研究和学习,为什么要使用Mars我这里做了几点小结:
        1 如果你的应用只是一个普通的APP,并不涉及到即时通讯,或者你们的APP对于消息的即时性没有很强的要求,那么我比较推荐你只单独使用Mars的Xlog组件就可以了。这对你们app的日志管理,线上问题追踪和定位具有非常好的帮助。在接入了Xlog后,出现线上问题我们上家公司几乎可以做到5分钟内定位问题,10分钟给出解决方案。
        2 STN组件的设计逻辑非常的贴合移动互联网的网络使用环境,是一套非常成功的解决方案,尤其是弱网环境和平台特性具有非常多的优化策略,尤其是移动端
        3 Mars 是基于socket的解决方案,在网络调优方面具有跟强的主动性和可控性。

        Mars 的超时机制

        TCP的超时重传

        熟悉TCP传输协议的同学都知道,TCP在建立连接的情况下,如果当前的请求方没有在规定的时间内得到接收方的相应,那么就会发生重传的机制,这个也是TCP保证可靠性传输的一个重要的原则。我们在了解超时和重传是需要了解的两个指标
        RTT:数据往返的时间;
        RTO:超时重传的时间间隔
        这里盗一张mars的官方图,我们来看下Android手机的tcp超时重传的时间间隔表现


        从图中我们可以看到,超时重传的间隔,依次为[ 0.25s,0.5s,1s,2s,4s,8s,16s,32s,64s,64s,64s …]
        我们可以看到,前面的重传时间还有一定的时间间隔,但后面是几个连续的64s。这个算法其实就是指数退避的算法。

        Mars的超时重传机制

        是不是传输层已经有了超时重传机制,应用层就不需要了呢?其实不是的,我们可以知道tcp在经过一定的超时重传后才会确定当前的tcp不可用,返回timeout标示。但是这个时间非常的就,一般来说Android手机大概要6min才会确定当前的TCP连接不可用。但是对于移动端来说6min的时间是非常影响用户体验的,因此应用层的超时重传机制需要更加的敏捷。
        但是需要注意,敏捷并不等于密集。在很多的场景下,密集的心跳机制并不能取到重新建连的效果,反而会是网络通道更加的恶劣。因此在经过多个尝试后,Mars采用了一下的几个超时方案

        总读写超时

        总读写时间这个比较好理解,就是一次完成的RTT所需要的时间,这里总结起来包括:
        请求发送耗时 - 类比TCP包传输耗时;
        响应信令接收耗时 - 类比ACK传输耗时;
        服务器处理请求耗时 - TCP接收端接收和处理数据包的时间相对固定,而服务器由于信令所属业务的不同,逻辑处理的耗时会差异明显,所以无法类比;
        等待耗时 - 受应用中请求并发数影响。
因此,我们提出了应用层的总读写超时如右图所示,最低网速根据不同的网络取不同的值。

        首包超时

        总读写超时有一个弊端是无法确定服务端的处理请求时间,最致命的一点是相应信令的时间处理较长,这导致了在网络状态很差的情况下,同样需要较长的时间才可以进行重试。首包超时是指在tcp的第一次回包的情况就去确定这个信令的接受耗时。调整后的首包超时方案如下

        包包超时

        首包超时有一个弊端,就是在网络堵塞的情况下,由于tcp的拥塞窗口和流量控制,一个tcp包会被切割成几个部分进行传输,也就是发生了tcp的拆包和粘包的现象。加入后续的包又丢失了,仍然需要整个完整的读写超时才能发生问题。这就引入了包包超时的机制:两个数据段之间的超时时间。因为包包超时在首包超时之后,这个阶段已经确认服务器收到了请求,且完成了请求的处理,因此不需要计算等待耗时、请求传输耗时、服务器处理耗时,只需要估算网络的 RTT。

        动态超时

        动态读写超级更多的是依赖于当前的网络状况,这个网络状态评估就跟之前提到的SDT模块有着莫大的关系。Mars在动态超时的设计中,把当前的网络分为excellent和evaluate两种状态,分别在这两种状态中不停的调整当前的动态耗时。

        Mars 的连接方案-复合连接

        我们先总结一下串行连接和并行的优缺点
        串行连接
        1 资源占用小,服务端没有负载的压力
        2 超时选择困难,连接最慢可用
        并行连接
        1 资源占用较大,服务端负债压力大
        2 连接最快可用
        复合连接
        先看一下官方给的图

                                                            

        
        初始阶段,应用发起对 IP1 &Port1 的 connect 调用。在第4秒的时候,如果第一个 connect 还没有返回,则发起对 IP2 &Port2 的 connect 调用。以此类推,直至发起了5组 IP&Port 的 connect 调用。
        对比串行连接与并行连接,复合连接有以下特点:
        常规情况下,服务器负载与串行连接策略相同,实现了低负载的目标;
        异常情况下,每4s发起新(IP,Port)组合的 connect 调用,使得应用可以快速的查找可用 IP&Port,实现高性能的目标;
        在超时时间的选择上,复合方式的“并发”已经实现了高性能、低负载的目标,因此在超时时间的选择上可以相对宽松,以保障高可用为重。
        综合对比,复合连接能够维持低资源消耗的情况下,能同时实现低负载、高性能、高可用的目标。

        Mars的智能心跳

        智能心跳指的是长连接过程中,应用层维护的自己和服务端的心跳连接。客户端在适当的时间周期内,向服务端发送一个心跳请求,判断当前的连接是否可用。一般的app处理是用一个定时的任务(45s)连续的向服务端发送ping请求,等待服务端的返回。如果心痛不同,则认为当前的长连接不可用,需要重新进行长连接的建联。
        微信的智能心跳如下
        心跳时间区间:最小4分30秒,最大9分50秒;
        心跳增加步长:60秒;心跳稳定后,探测步长:20秒;
        当前APP为活跃状态,长连刚连接前3个成功心跳,和没有网络,这3种情况使用固定最小心跳:4分30秒;其他情况使用自适应智能心跳,基本算法如下;
        连续3个心跳成功后,每心跳增加60秒心跳步长,一直到最大9分50秒,设为固定状态;
        连续3个心跳失败后,减少60+20秒,第4个心跳失败,直接设最小心跳4分30秒;


 

工程的心跳跟超时重传机制

一、前言
        1.1 心跳机制
        长连接的方式给我们带来了很多好处,如果要让消息通过长连接实现可靠投递,最关键的环节就在于要维护好这个长连接。
        要维护好这个长连接,一个关键的问题就在于能够让这个长连接在中间链路出现问题的时候,连接的两端能快速得到通知,然后通过重连来重新建立新的可用连接,从而让我们这个长连接一直保持高可用状态。
        这个能够快速、不间断识别连接可用性的机制,被称之为心跳机制。心跳机制通过持续的往连接上发送“模拟数据”来试探连接的可用性,同时也让我们的连接在没有真正业务数据收发的时候,也持续有数据流通,而不会被中间的网络运营商以为连接已经没有在使用而把连接切断。


        1.2 心跳机制的必要性
        1. 降低服务端维护连接的开销。心跳机制可以让服务端能尽快感知到连接的变化,从而尽早清理服务端维护的相应连接。对于大部分的即时通讯场景,手机
信号强弱变化或者路由故障等都可能会导致长连接处于实际不可用的状态;
        2. 支持客户端断线重连。对于客户端发出心跳包,如果在一定时间内都没有收到服务端的响应,那么客户端可以认为和服务端的长连接不可用,这时候客户
端可以断线重连;
        3. 连接保活。心跳机制还有一个重要的任务就是尽量让建立的长连接存活时间更长。因为很多运营商为了节省资源和降低自身网关的压力,对于一段时间没
有数据收发的连接,运营商会将它们从NAT映射表中清除掉。那么,如果客户端能在没有消息收发的空闲时间给服务端发送一些消息,就能避免长连接被干掉
了。


二、心跳机制介绍
        2.1 双向心跳保活机制
        IRC消息系统的心跳保活机制是双向的:
        1.即客户端会主动向服务端发送心跳(Ping)包,服务端在收到心跳包后需要回给客户端心跳响应(Pong)包;
        2. 在某些情况下,服务端也会主动向客户端发送心跳(Ping)包,客户端在收到心跳包后同样需要回给服务端心跳响应(Pong)包。


        2.2 客户端ping<=>服务端pong
        ping_interval:心跳间隔时间。接收端:8s,发送端:5s
        ping_timeout:心跳超时时间。接收端:20s,发送端:8s
        如上图所示:客户端在与服务端建立好长连接之后,会定时间隔(ping_interval ms)向服务端发送心跳(ping)包,服务端在接收到心跳包之后,会立马回复
心跳响应(pong)包。
        
        2.3 服务端ping<=>客户端pong
        2.3.1 定时器CheckUserLiveTimer
        在消息系统服务端接入层(AccessServer)上,有以下逻辑:
        1. 对于连接在AccessServer上的每个用户都会维护一个属性: lasttime。lasttime在以下两种情况会被置为当前时间(TNOW):
        用户与服务端成功建立连接后,服务端将用户信息记录下来时;
        服务端在收到该用户发送的任意数据包(心跳包、业务数据包等)时。
        2. 服务端AccessServer启动后,会启动一个定时器CheckUserLiveTimer,CheckUserLiveTimer每隔2s,会检测一次所有在该AccessServer上的用户的存活情况(即心跳是否超时)。

        2.3.2 心跳超时检测
        1. 在某些情况下,服务端是会主动向客户端发送心跳包的,即双向心跳保活机制;
        2. 在服务端检测到客户端心跳超时后,并不会立即断开连接,而是会再尝试主动给客户端发送心跳包来检测连接是否真正不可用;
        3. 服务端在多次发送心跳包给客户端后,若还是未收到客户端的任意数据包,服务端就会主动断开连接。


多进程心跳优化方案


一. 背景
        多进程版本中存在对插件进程的心跳检测,如心跳出现异常,主进程则会根据规则判断杀死子进程,目前采用主进程到子进程的单向心跳,即主进程在固定时间间隔后向子进程发送Ping消息,子进程或子进程插件收到后向主进程恢复Pong消息;
        目前上线后发现心跳存在以下几个问题:
        崩溃发现的比较晚: 子进程崩溃后,主进程发现子进程崩溃的时间间隔比较长,一般为[ 0 -心跳时间间隔(5s)+心跳发送间隔(5s) ],因此最坏情况可能在双倍的心跳间隔时间后才发现子进程崩溃。
        崩溃中的消息丢失:在子进程崩溃后,到主进程发现子进程崩溃期间,如上层向插件进程发出指令,则此指令会被丢失,并且崩溃恢复后无法复员
这些指令。
        心跳检测的误杀: 对于针对进程检测的子进程启动,目前的机制是先开启心跳检测,然后等待子进程启动后恢复心跳,期间依赖一个经验值的等待时间,容易造成子进程启动完导致第一次启动超时被杀死。
        重点进程无法灵活调整心跳:一些插件比较活泼,而一些插件并不是经常被调用,但是两者的心跳检测方式是一样的,等待的时间也一样,不合理。
        目前专门为了解决以上4点问题,进行了心跳优化。

二.方案
架构
        旧的心跳模式:
        Watcher:用于维护子进程每个插件的心跳,通过发送心跳Ping,处理心跳Pong,向上抛出Broken事件决定插件所在进程是否崩溃。对于插件监测的进程每个
被监测的插件具有一个Watcher,对于进程监测的一个进程具有一个Watcher。
WatcherHost:用于维护该进程中的所有Watcher。

        
        新的心跳模式:
        主要改动: Watcher中去掉老的通过PostDelayTask方式维护心跳时间的方法,加入两个模块Timer和StateMechine用来维护心跳的延时和发送状态。

三. 核心思想:
        1.依然使用配置文件中的心跳间隔时间heartbeat_interval,当满足此时间没有心跳返回才判定插件进程崩溃。

{
    "extension_process_id": "tal_live",
    "heartbeat_monitor_type": "monitor_extension",
    "heartbeat_interval": 10,
    "extension_config": [
    {
    "extension_id": "live",
    "enable_heartbeat_monitor": true,
    "heartbeat_interval": 2
    }
    ]
}


        2.利用插件向主进程抛出的callback消息作为Pong消息的替代,在有callback时当作一次Pong消息,将下次发送Ping消息的时间延迟。
        (1). Timer:是一个延时的定时器,当接收到心跳Pong或MessageCallback消息时,会触发拓展延时,当时间超时后会被触发。

        
        (2). Statemechine: 用于维护发送Ping消息,并且切换插件活跃状态的状态机,当状态走到Stop时认为插件崩溃。


状态切换流程: 

 
        (3).插件崩溃
        当PingWait3rdState或PingWaitClamState收到Timout消息后,切换到StopState并向上层WatcherExtension回调,恢复插件进程的逻辑与之前相同。
        (4).频率的计算
        每个Watcher维护一个最近10条消息的队列(包括Pong消息),如最近10条的消息的平均间隔时间小于1/4 interval,则认为此插件与主进程沟通频繁,则判断
为高频,否则则为低频状态。
        (5).消息的保存
        当ClamState, LivelyState, PingWait1stState, PingWait2cdState在收到TimeOut后,会向子进程发送Ping消息,并向上层上报 PingStart回调。当ClamState, LivelyState, PingWait1stState, PingWait2cdState在收到HeartBeat或Message后,会向上层上报PingStop回调。
        主进程在两次回调期间,会将所有发送给对应进程的消息缓存,如果收到PingStop则抛弃缓存不错重发,表示心跳正常。如收到了WatcherExtension则表示插件崩溃,启动后重发消息,并清空缓存。
        (4).进程监控加入initfinish
        对于进程监控的配置,在主进程与子进程消息3次握手之后,子进程会发送initfinish消息到主进程(之前只有插件监控有这个逻辑),主进程在收到initfinish后,才开始对这个进程进行监控。

四.流程示例

        

参考链接

        【深入了解微信Mars基础组件 - 简书】        

        【微信Android客户端后台保活经验分享

        【Android的微信智能心跳方案_android心跳实现方式_火山石的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/John_ToStr/article/details/130907097