IM推送Android客户端SDK之智能心跳

微信的智能心跳策略

微信智能心跳策略

1. 为什么TCP连接需要心跳?

因为运营商有一个NAT超时:因为IP v4的IP量有限,运营商分配给手机终端的IP是运营商内网的IP,手机要连接Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网IP、端口到内网IP、端口的对应关系,以确保内网的手机可以跟Internet的服务器通讯,大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰NAT表中的对应项,造成链路中断。
所以我们需要间隔一定的时间发送一个数据包来保证当前的TCP连接保持有效,这就是所谓的心跳包

2. 什么是智能心跳?

智能心跳实际上就是动态的探测到最大的NAT超时时间,然后选定合适的心跳间隔区间去发送心跳包,同时在网络状况发生变化的时候能够动态的调整心跳间隔时间;如果心跳间隔不合适,例如心跳间隔过短,那么可能导致频繁的唤醒手机发送心跳包,增加耗电,心跳间隔过长,可能导致这条TCP连接已经无效但是无法及时的检测到,只能等待下一个心跳包发送的时候才能感知到,所以会导致消息接收延迟,所以探测到一个合适的心跳间隔是非常重要的,把耗电和消息接收及时性综合折中来取得一个最佳的体验

IM心跳策略

心跳的字段定义

  • minHeart 最小心跳,本地默认120秒,服务器定240秒
  • maxHeart 最大心跳,本地默认580秒,服务器定580秒
  • startHeart 起步心跳,本地默认240秒,服务器定240秒

心跳信息字段

  • networkTag 当前网络类型,如CMCC-4G
  • stabled 稳定心跳的标志位,true表示稳定心跳
  • stabledSuccessCount 稳定心跳连续成功次数
  • failedCount 心跳连续失败次数
  • successCount 心跳连续成功次数
  • curMinHeart 当前心跳探测区间极小值
  • curMaxHeart 当前心跳探测区间极大值
  • curHeart 当前心跳
  • successHeartList 成功心跳列表

重置心跳

  • 当TCP连接有除了心跳包以外的消息包在进行传输(read)时候,就认为该TCP连接在这个时刻仍然有效,在程序中read到消息包数据后会对数据进行短时间处理(ms级别),然后再write数据,只有收到同步通知,或者单推的时候本地发现消息已经同步,那此时就不会write,不过这种情况发生的概率比较小,所以心跳是在write数据出去的时候进行重置,这里不在read数据的时候重置心跳是为了避免在弱网环境下,数据包要在网络中传输几分钟,导致服务器连接超时,然后把TCP连接误断的这种情况
  • 如果心跳包在write的时候进行重置,当遇到此TCP已经是无效连接,但是服务器和客户端都没有感知到这中情况,那么客户端对于write出去的消息会有一个超时检测(20s,但是消息ack没有超时检测),write数据出去后收不到响应的回馈,20s超时到期,此时会通过心跳来验证TCP连接的有效性,心跳超时就进行断线重连,所以这里会有60秒以上的消息延迟
  • TCP无效连接,如果是客户端的消息ack数据发送出去但是服务端没有收到,那么将遇到两种情况,第一是服务器连接超时端开,第二是客户端下一个心跳检测发现TCP连接是无效的,然后断线重连,这里会有最多一个心跳周期的延迟

心跳策略图

IM推送Android客户端SDK之智能心跳_第1张图片

触发心跳上调

  • 探测期间的心跳发送成功并及时收到服务器的响应,这时候会执行心跳上调
  • 稳定心跳连续成功超过10次之后会触发尝试心跳上调

心跳上调策略

  • 记录成功心跳的信息
    1. if (stabled == true) stabledSuccessCount++;
    2. successHeartList.add(curHeart);successCount++;
    3. if (successCount >= 2) failedCount = 0;心跳连续成功两次,才认为当前心跳在该网络环境下运行稳定
    4. 把当前的心跳信息更新到文件中。
  • if (stabled == true && stabledSuccessCount >= 10),那么执行以下操作:
    1. stabled = false;从successHeartList中筛选比当前心跳大一级的心跳周期作为当前心跳;curHeart = successHeart;
    2. 如果(1)没有筛选出结果,则用二分法进行上调:
      (1)curMaxHeart = maxHeart;
      (2)curMinHeart = curHeart;
      (3)如果发现curMinHeart > curMaxHeart那么curMinHeart = curMaxHeart;
      curHeart = (curMinHeart + curMaxHeart) / 2;
  • 如果当前心跳不是稳定心跳,那么执行以下操作:
    1. 从成功心跳列表中筛选比当前心跳大一级的心跳周期作为当前心跳:curHeart = successHeart;
    2. 如果1没有筛选出结果,则用二分法进行上调:
      (1)curMinHeart = curHeart;
      (2)if (curMaxHeart < curMinHeart) curMaxHeart = curMinHeart;
      (3)curHeart = (curMinHeart + curMaxHeart) / 2;
  • 判断curHeart > maxHeart,如果是则curHeart = maxHeart;stabled = true;这是用来异常过滤
  • 检测心跳探测区间是否达到机值条件:
    1. if (curMaxHeart - curMinHeart <= 10 && stabled == false) curHeart = curMinHeart;
    2. stabled = true;
  • 使用curHeart进行下一个心跳的发送

触发心跳下调

  • 心跳会在TCP连接成功后,设备login成功之后才会start,否则将不启动心跳
  • 探测心跳请求超时(20秒)后,会补发一个尝试心跳,尝试心跳再次超时,那么就会断开TCP连接,判断刚才的连接持续时间是否已经超过最小心跳的周期,如果超过了并且此时心跳已经启动,那么执行心跳下调策略,否则不下调心跳
  • 目前会触发心跳下调的情况:
    1. NAT超时,运营商断开
    2. 存在一个bug,tlv数据解析失败,会断开连接,这时候心跳会误下调,现在已经修复(心跳只有在设备login成功后才start,而只有start状态下的心跳才有下调的资格)
    3. 弱网环境下,网络连接频繁的断开,会造成心跳的误下调,目前误下调的稳定心跳只有在连续成功10次之后才会再次上调

心跳下调策略

  • 记录心跳失败信息:
    1. 从successHeartList移除当前心跳锁对应的心跳周期;stabledSuccessCount;successCount;failedCount++;
    2. 把当前的心跳信息更新到文件中。
  • if (stabled == true && failedCount >= 3)那么执行以下操作:
    1. stabled = false;
    2. 从successHeartList筛选比当前心跳小一级的心跳heart
    3. if ((minHeart + curHeart) / 2 < heart) selectedHeart = heart;curHeart = selectedHeart;
    4. 如果没有筛选到适合条件的selectedHeart,那么就进行二分法下调:
      (1)curMinHeart = minHeart;
      (2)curMaxHeart = curHeart;
      (3)if (curMaxHeart < curMinHeart) curMaxHeart = curMinHeart;
      (4)curHeart = (curMinHeart + curMaxHeart) / 2;
  • if (stabled == false && failedCount >= 3)那么执行以下操作:
    1. 从successHeartList筛选比当前心跳小一级的心跳heart;
    2. if ((minHeart + curHeart) / 2 < heart) selectedHeart = heart;curHeart = selectedHeart;
    3. 如果没有筛选到适合条件的selectedHeart,那么就进行二分法下调:
      (1)curMinHeart = minHeart;
      (2)curMaxHeart = curHeart;
      (3)if (curMaxHeart < curMinHeart) curMaxHeart = curMinHeart;
      (4)curHeart = (curMinHeart + curMaxHeart) / 2;
  • 检测心跳探测区间是否达到机值条件:
    1. if (curMaxHeart - curMinHeart <= 10 && stabled == false) curHeart = minHeart;
    2. 之所以加入这个判断是为了当心跳一直失败下调,但是curMinHeart和curMaxHeart又很接近导致二分法无法下调的时候,就直接把curHeart设置成minHeart

稳定心跳

  • 当探测到稳定心跳之后,正式使用的心跳值会在探测到的稳定心跳的基础上扣除40秒,避开临界值

alarm不准的问题

  • 在Android6.0系统以上,休眠的时候alarm是会被延迟执行的,可通过加入系统白名单的方式来避免,Google的GCM就是默认系统白名单,但是在手机上,系统白名单尝试过,并没有用;手表是原生的Android系统,可以尝试加入白名单更加可靠

“`

4. 最终探测效果

  • 以270秒作为curHeart开始探测,minHeart为60秒,maxHeart为300秒,在我们公司的wifi或者数据网络环境下:270,285,292就能够达到稳定心跳,最终稳定心跳会比292小10秒。也就是282秒作为稳定心跳,这里面大概在14分钟之内alarm了三次,如果把maxHeart上调的话探测到稳定心跳的时间会变长,不过平均alarm次数会降低,因为心跳周期在不断变长
  • 当达到稳定心跳后,在稳定心跳成功发送20次后会再次尝试上调心跳,如果由于网络环境不稳定导致当前的心跳可能失败次数超过了5次,那么就会下调心跳,总之做到一个原则,严格控制下调条件,能不下调就尽量不下调

5. 和微信智能心跳的对比

  • 更加省电:微信智能心跳是按照从最小还是逐渐递增的去探测的,所以在网络环境不好的条件下前期可能一直探测不上来,心跳周期一直维持在一个较小的范围,导致频繁的alarm,耗电,微信智能心跳探测过程:60秒短心跳,连续发3次后开始探测,90,120,150,180,210,240,270,这个过程中一共耗费24分钟,alarm了10次,在前14分钟之内alarm了8次,而二分法智能心跳前14分钟才唤醒3次
  • 网络环境差的情况下不会频繁的唤醒:当网络环境很不好的情况下,心跳可能会经常失败,微信智能心跳由于是从下往上上调心跳,可能一直维持在一个间隔周期较小的心跳,会频繁alarm,二分法是从上往下下调心跳,因此心跳周期是逐渐缩小,一开始不会频繁的alarm,比较省电
  • 探测周期短:微信智能心跳是逐渐的通过累加探测步长来上调心跳,上调的趋势比较稳定,但是如果step设置的比较小,那么会导致上调缓慢,探测到稳定心跳所需要的时间比较长(24分钟);二分法智能心跳的心跳调整波动比较大,成功了就上调一半,失败了就下调一半,所以探测到稳定心跳的时间会比较短(14分钟),但是其实这个都是相对的,如果NAT超时时间为2分钟,那么微信智能心跳一下子就能探测到了,而二分法智能心跳要调整好多次,反正是看NAT超时时间距离最初开始探测的curHeat比较接近,所以curHeart可以通过大数据搜集分析,针对各个地区给出不同的curHeart
  • 探测期间不够稳定:微信智能心跳的探测过程很稳定,基本不会导致心跳失败,因为它是从最小的开始探测;二分法智能心跳就不一样了,以为curHeart的调整波动比较大,一开始探测一下子上调或者下调一半很容易就超出NAT超时时间,在探测前期会有比较频繁的失败心跳;当然,这个也是相对的,最终都要取决与curHeart的初始值,minHeart,maxHeart,如果这些值设置的合适,那么二分法智能心跳将会很快的探测到稳定心跳

6. Android机子上存在的问题

  • alarm的对齐唤醒:国内的手机厂商例如华为,魅族,小米都是自定制的android系统,对于AlarmManager都有对齐唤醒策略,因此会导致心跳alarm的时间不准确,例如设置了270秒alarm一次,但是在这些手机上可能要推迟到300秒才能唤醒,那么问题来了,如果NAT超时时间是2分钟,而这些手机的alarm最小间隔是5分钟,那就坑了,永远无法探测到最佳心跳,你设置120秒的alarm,手机系统也给你延迟到5分钟才执行alarm,不过这种情况只有在手机休眠的时候才会对齐唤醒,在手机不休眠的时候,我侧过,alarm计时还是准确的

你可能感兴趣的:(android)