导语
背景
经过调研,对比了各大厂家使用方案及推送相关技术点的特点,最终决定采用MQTT+ ProtocolBuffers基于长连接的数据实时推送的方案,并为该系统命名为:Hermes(信使)。
技术选型
推送SDK |
集成APNS | 保持长连接 |
极光推送 | 是 |
是 |
个推 | 是 |
是 |
百度推送 | 是 |
否 |
腾讯信鸽 | 是 |
否 |
本文方案 | 否 |
是 |
-
推送方案具有极大的不确定性,消息丢失率比较高(尤其是安卓端)。 -
iOS推送系统到达率可以理解为100%,到达率可以完美解决,但是推送并不能保证百分百实时到达,不能达到及时性的要求。 -
对于三方SDK采用的长连接协议并不可知,且数据传输格式,连接存在状态不受控制,可能导致未知问题。 -
三方SDK定位问题较为困难,沟通配合成本直线上升。 -
大量推送数据之后,难免需要付费,并且数据存在共享服务器中,服务质量是否有保障。
基于以上方案的分析,自建一套轻量级、可靠性高的推送系统才是这个需求的最终解决方案;
在技术选型的过程中,调研了MQTT及XMPP等传输协议,以及JSON、XML、Protocol Buffers等数据序列化协议,基于轻量级、可靠性的关键点,最终在其中选择了MQTT作为传输协议,Protocol Buffers作为数据序列化协议;详细原因如下:
2.1 MQTT
MQTT(MessageQueuing Telemetry Transport,消息队列遥测传输)是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,因此易于实现。这些特点使得它对很多场景来说都是很好的选择,包括受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT),这些场景要求很小的代码封装或者网络带宽非常昂贵。本协议运行在TCP/IP,或其它提供了有序、可靠、双向连接的网络连接上。它有以下特点:
使用发布/订阅消息模式,提供了一对多的消息分发和应用之间的解耦。
很小的传输消耗和协议数据交换,最大限度减少网络流量。
异常连接断开发生时,能通知到相关各方。
提供三种等级的服务质量:
消息传输不需要知道负载内容。
在上述特点中,特点1和特点2的存在使得MQTT协议天然具备了轻量级的特点;发布/订阅模式的存在,使得发布者和订阅者不存在直接联系,减少了连接通路的占用,节省了资源;打个比方:和一位朋友联系,可以选择打电话的方式,但是这样需要电话接通才能够开始交流,这种情况就属于请求/回应的场景,是一个同步场景,需要一直占用连接通路;或者可以选择发邮件,这样你发了电子邮件之后就可以释放资源,去忙其他的事情,朋友随时都可以去查看电子邮件,这就属于发布/订阅场景,是一个异步场景。
另外,MQTT尽可能的简化协议内容,最小头部只有2字节,能尽可能的提高传输成功率,减少因为网络差而引起的消息丢失;且MQTT在设计之初,就是在物联网低功耗、网络带宽有限的智能硬件上使用的,能更好的兼容低端设备。
特点4中的只有一次的服务质量策略可以保证我们消息推送的可靠性。
综上分析,MQTT是在此方案调研中较为适合的通讯协议。
2.2、Protocol Buffers
ProtocolBuffers,是Google公司开发的一种数据描述协议,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。比JSON更加轻量,传输时消耗更小的带宽。跨语言,这是它的一个优点。它自带了一个编译器--protoc,只需要用它进行编译,就可以编译出大部分平台的类文件;该协议具有以下特点:
小巧、便捷
简单、易用
Protocol Buffers小巧的特点,更能将MQTT传输的信息进行极致的压缩,做到传输体积更小的目的,进一步提高了传输成功率;简单易用的特点可以让我们轻松的上手,集成到项目中。
故在数据序列化协议上,Protocol Buffers是比较合适的。
上述方案,因采用MQTT 和Protocol Buffers两种协议,对弱网情况兼容较好,设备性能要求相对较低,且对数据格式优化比较好,且连接状态完全可控,可以实时监控到链路是否可用,对分析问题、解决问题有很大便利。
技术方案实践
1、方案调研
基于需求的场景,车分期的销售用户,需要及时知晓订单的审核状态,需要服务器的审核状态及时传送到客户端,此场景是在用户使用APP过程中回调,故在APP存活过程中保持长连接的存在是此次方案主要解决的问题;APP在被杀死之后消息的到达,可以通过接入推送系统来解决,但由于时间紧迫,系统一期优先实现方案如下,技术方案大体流程如下图:
信使系统大致流程图
在整个过程中,消息的实时传送靠推送后台和长连接SDK建立的长连接来实现;而基于本方案实现的SDK则负责和推送后台维持长连接的有效性以及维持长连接的优化功能;
详细流程中涉及到的交互方和交互流程参照下图:
接入信使系统交互图
2、方案架构
信使系统架构图
在整个数据实时传送方案中,涉及到长连接的建立维护、通信数据按协议封装、业务数据封装及优化等功能,在此对整个方案运用到的技术点放进系统架构图中进行抽象整理,整理之后结构如下:
在整体结构中,分为业务层、应用层、传输层、和网络层,其中业务层、应用层、传输层组成了数据实时推送服务的SDK;
下面对整个SDK架构层级,以及各层级间包含的模块进行详细整理及说明;
2.1、业务层
该层主要解决,数据格式转化,消息丢失、重复,消息存储,数据统计等功能;下面对这些功能逐一介绍:
数据格式转化: 此模块负责将Protocol Buffers类型的数据转化成JSON数据。
消息重复、丢失处理:此模块会根据规则判断收到的消息是否重复,或者在收到该信息之前是否丢过消息,经过处理之后,如果重复则不再传给业务方,如果有消息丢失也将告诉业务方,以便业务方处理相关情况。
消息存储:根据一定缓存策略对消息进行短时间存储,以便解决消息判重、处理丢失等问题。
数据统计:此模块将对消息进行统计,区分重复、丢失等情况,并将数据上报服务器,方便验证整个方案的可行性和问题等。
2.2、应用层
该层命名的来源主要来自于MQTT,因MQTT属于业务层协议,该层的操作绝大部分是对协议的实现以及优化;主要包含心跳机制、断线重连机制、连接状态管理等;
心跳机制:此模块为该方案主要技术点,下文会独立讲解。
断线重连机制:此模块为该方案主要技术点,下文会独立讲解。
连接状态管理:此模块负责对应用前台、后台不同状态情况下,连接的处理情况的管理。
2.3、传输层
该层主要为调用系统API对Socket数据进行拼接组装,以及发起连接、关闭连接等操作;
对于该方案的具体模块,以上均已介绍完毕,以下是对该方案大致流程的梳理,如下图:
信使系统流程图
此流程展示出了SDK从建立连接、接收数据,经由TCP数据包处理、MQTT通信数据解析,到Protocol Buffers数据格式处理等大致流程。
3、技术要点
3.1、心跳策略
说到心跳保活的策略,那么我们可能要回到心跳包存在的的意义以及为什么长连接必须要有心跳包,那么很容易就引出以下两个问题:
什么是心跳包,心跳包的存在到底是为了解决什么问题;
为什么TCP的KeepAlive不能替代应用层心跳保活机制;
首先,心跳包是为了客户端和服务端为了确保对方存活而每个一定时间发送的数据包;那么,心跳包存在的意义在哪里呢?其实我们都知道,如果客户端和服务器都处在同一个稳定的网络,并且都只处理和对方连接好的长连接,那么就算过一段比较长的时间,那么长连接也是会存在的;但是很显然这种情况是不会存在的,客户端和服务端不可能只处理这一个连接,而且网络也不可能一直处于稳定状态,尤其在客户端是手机这样的移动设备的情况下,这样很容易就带来了以下两个问题:
移动网络的NAT超时
由于IPv4的IP数量有限,运营商分配给手机终端的IP是运营商内网IP,手机要连接互联网,就需要通过运营商的网关做一个网络地址转换(Network AddressTranslation,NAT)。简单的说运营商的网关需要维护一个外网IP、端口到内网IP、端口的对应关系,以确保内网的手机可以跟网络上的服务器通讯。大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰NAT表中的对应项,造成链路中断。
网络状态的切换
手机网络和WIFI网络切换、网络状态不好断开又重新连上等情况有网络状态的变化,这样会使长连接变为无效连接。需要监听响应的网络状态变化事件,重新建立Push长连接。
从以上两个问题得出,首先,心跳包的存在可以解决移动网络NAT超时问题,并且长连接心跳包的间隔时间必须要小于NAT超时时间,如果超过NAT超时时间不做心跳,TCP长连接链路就会中断,服务端就无法发送消息给手机,只能等到客户端下次心跳失败后,重建连接才能取到消息。其次,心跳包可以探测连接是否断开以及客户端和服务端的存活。
其次,TCP中的KeepAlive回去探测TCP连接的死活,那么为什么不能用来替代心跳包呢?听起来两者似乎是一件事情,但是本质上却有很大区别:
TCP中的KeepAlive是保持TCP连接的存活,也就是说它保持了连接通道的存在。
心跳包不仅探测了长连接的存在,且确定了长连接两端的客户端和服务端的存活。
还是和朋友打电话的例子:KeepAlive可以保证你和朋友的电话是通着的,但是确不能保证你和朋友都在电话旁,而心跳包不仅保证了电话通了,而且保证了你和朋友都在接听电话。
这样就很好理解了,假如服务器负载过高,无法响应请求,此时TCP探针依旧认为连接是存活的,但其实服务器已经重启,原来的连接已经不能再给客户端发消息了;而心跳包可以发现这个问题,并会发起重连,假如服务器在很短的时间内重启成功,那么还是不会影响我们消息到达的;从另一方面来说,客户端可能已经被用户杀死,并不在需要这个连接存在了,服务端也可以通过收不到心跳包来确定此连接已不再需要,及时的回收资源,避免资源的浪费。
基于对以上问题的探索,我们很容易得出:心跳包主要是为了解决移动网络NAT超时问题,那么从这个点出发,我们就可以得出系统心跳时间间隔的决定因素:各大运营商NAT超时时间。
只要心跳包间隔小于这个NAT超时时间,那么我们就可以最大可能的节省资源消耗的前提下,保证连接的可用性。
据网络查询出的结果可得到部分运营商NAT超时时间:
运营商 |
NAT超时时间 |
移动 |
5分钟 |
联通 |
5分钟 |
电信 |
大于28分钟 |
根据以上数据,结合需求需要实时性较高,用户需要得到的信息一般在一两分钟就会返回结果,故方案现采用120s的时间间隔作为心跳频率。
3.2、断线重连机制
因各种不确定情况的发生,连接不可能一直存在,故需要设计断线重连机制,需要从以下两方面考虑:
因存在网络切换、移动网络基站变更、以及网络断开等问题。
服务器异常导致的断线重连,如负载过高;为了防止服务器异常断线之后,造成客户端集体掉线的情况,客户端会立刻发起重连,在用户量较小的情况下,客户端集体重连对于服务端可以承受,但当用户量级达到一定数量,大量客户端的重连,可能就会产生重连风暴,直接导致服务端再次出现异常,因此对于重连机制也应考虑一个比较合适、能避免重连风暴的策略。
在这里,采用了将重连间隔随机分配在15s内进行,也就是说客户端在检测到连接断开之后,会随机的在15s以内的一个时间间隔进行重连,这样就可以将重连风暴进行削峰,大大的减轻了服务器的压力,避免了服务器再次出现异常。
为了避免服务器一直异常,客户端却一直重试造成电量、流量的浪费,重连机制会选取6次1~15s之间的随机时间间隔进行重连,减少了电量、流量的消耗,提升用户体验。
到此,整个现有方案结束。
未来优化方向
考虑到移动端手机电量、流量、及网络连接不稳定的情况,上述方案可以满足现实需求,但显然不是完美的方案,针对上述方案,现有以下规划:
1、实现杀死APP的情况下消息的推送
为进一步提升用户体验,完善推送系统,实现APP在杀死的情况下,也可以接收到推送消息,对于iOS开发中来说,由于苹果公司的限制,在APP没有启动的情况下,只能通过APNS来进行推送,故,后续接入苹果APNS即可实现该功能,完善之后的流程图如下:
推送流程图
2、智能心跳策略
因心跳是保证连接存活的必要手段,心跳的存在毋庸置疑,但是心跳的存在,也必定会增加电量、流量的消耗;故心跳间隔需要在可以保证NAT不超时的情况下尽可能的久,因此我们可以从一个固定心跳入手,去尽可能的探测心跳的最大步伐,去减少流量和电量的消耗。
总结
本文通过信使系统的实践过程,总结出信使系统的架构,以及在过程中遇到的技术点,以及一些解决方案,在实践过程中,不仅解决了业务需求问题,并在一定程度上提升了用户体验;经过线上验证,信使系统消息到达率达99%以上,且消息及时性较为可靠,很好的满足了车分期项目的使用;但是一个系统从来都不是一蹴而就的,都要经过不停的迭代与优化才能越做越好,这个过程将会是一个漫长且繁琐的过程,为了支撑业务完美运行,并且给用户以更好的体验,我们更应该严格要求,设计更好地优化方案,提高编码质量,使系统日趋完美。
2、Protocol Buffers官方主页:https://github.com/52im/protobuf
3、MQTTClient开源库:https://github.com/novastone-media/MQTT-Client-Framework
END
阅读推荐
58同城宝实时数仓建设实践
Android字节码优化工具redex初探
本文分享自微信公众号 - 58技术(architects_58)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。