消息推送架构介绍

推送是在日常终端使用场景中经常碰到,特别是移动互联网普及之后,手机终端成为了消息推送的主战场,例如生活服务类的优惠券推送,咨询类的新闻推送,电商类的购物推送等等,在业务用户触达上起到了至关重要的作用,那我们今天就来揭开一下推送这个隐藏在业务背景之下的技术实现。

目前,移动端获得消息通知主要有两种方式:pull(拉)方式和push(推)方式,下面分别对这两种方式做简要介绍。

pull方式

pull方式即“拉方式”,这种方式中手机上的应用程序在启动时及经过一定周期会定时连接应用的服务端来获得服务器需要传递给终端的消息,因为此处是终端从服务端主动获得消息,因此称为拉方式。 
此方式服务端实现简单,只需要在终端连接上之后把需要发送的消息发送给终端即可,但是此方式有如下弊端:
1、每个应用终端都需要建立到自己服务器的socket连接,移动终端需要维护多个socket连接,较为耗电,不易于管理。
2、采用拉的方式,应用在启动的时候会从应用的服务器上拉取消息;启动之后,应用会周期性的连接服务器去检查是否有消息需要拉取,这种方式并不实时,需要等到终端主动拉取的时候服务器才能把消息传递到终端。如果应用频繁检查是否有消息需要拉取,那么耗电会增加,如果检查周期过长,那么会影响消息的实时性
综上,采用pull方式进行通知消息的传递并不是一个很好的方法。

push方式

采用push方式主要有点如下:
1、用push方式,移动终端只需要和推送服务器之间保持一个长连接即可。这样移动终端用于推送的socket连接数量就与需要推送服务的应用数量无关了,只需要维持一个终端与推送服务器之间的长连接即可,所有应用的服务端都是直接连接推送服务器并通过推送服务器来把消息推送到终端。而终端也只与推送服务器进行连接即可获得推送的通知消息。
2、推送服务器通过长连接,在消息到来的时候可以立即把消息推送到连接上来的终端上,实时性比较高


一个典型的消息推送系统设计
设计通知推送系统需要考虑的几个问题
推送应用认证问题:使用推送系统的应用首先需要认证,只有认证通过的应用才允许使用推送系统推送消息。
多个推送服务器的负载均衡问题:通过在推送应用服务端程序与推送服务器之间放置tcp负载均衡服务器来实现。
移动端服务器连接负载均衡问题:通过DNS负载均衡来实现。
推送消息时,移动终端在线,则消息应该立即送达。
推送消息时,移动终端不在线,那么分两种情况:对于按序消息,应该每条消息都保存,在移动终端上线时按照顺序由移动终端主动拉取,此时,新的推送消息要等待之前的消息都被接收之后再实时推送;对于覆盖消息,只保留最后一条消息,在移动终端上线之后直接实时推送消息。

消息推送系统用例图

  • 此图展示了实际应用场景中消息推送的关系。
  • 在实际应用场景中,需要推送的应用服务端只与推送系统进行交互,它并不知道是否有及有多少终端需要被推送消息。服务端把需要推送的消息交给推送系统,其余的就不是应用服务端需要关心的事情了。
    è¿éåå¾çæè¿°
    消息推送系统设计图
    è¿éåå¾çæè¿°
     

    推送应用1,2,3为推送应用的服务端,其负责把需要推送的消息放入推送系统。这些应用服务端通过负载均衡服务器来连接到具体的推送服务器。

    服务端是Socket.io的集群,供客户端(Web、移动端)连接。集群后面是一个Redis服务器,保存集群中每个节点(我们称之为Cluster)连接的客户端ID。同时Redis里面为每一个Cluster分配了一个队列,保存推送到这个Cluster的消息。

    当有消息从某个客户端发出后,所连接的Cluster从Redis里面获取这个消息的目标客户端ID(由于我们同时支持一对一私聊和群组,因此一条消息可能会被推送到多个客户端),然后把消息Push到每个Cluster的消息队列里面。

    每一个Cluster都会以阻塞方式读取它所对应的消息队列,一旦发现有消息,就获取并且查看其目标客户端ID是不是连接在这个Cluster上。如果是,就通过Socket.io发送,如果不是就丢弃。然后继续阻塞读取,直到下一条消息到达。

 总结:

其实粗略来讲,即时通讯-消息推送只是一种实现,比如:你可以用第三方产品,很轻易的就可以实现点对点、甚至点对多的消息收发。但是在用户需求很个性化,比如:我要对用户的聊天内容进行监控,涉及到敏感的关键字不让消息推送出去、或者我要对开通会员的用户给予“尊贵的身份”。

相比于免费用户,可以在云端保存时效更久的聊天记录或者可以添加的好友数、群数更多或者无上限,这时候对定制化的要求就非常高,毕竟数据是宝贵的。这时候我们就需要自行开发不能依赖第三方服务。

系统架构及模块介绍

消息推送架构介绍_第1张图片

这是一个比较完整的推送业务架构图,分为三个部分:业务层、通道层和客户端常驻服务,一般来说客户端常驻服务和通道层维持一个长连接通道实现数据的双向传递,而业务层实现的是基于推送业务形态的展示,例如推送的定时任务推送,接口推送,以及消息类型定义等等,我们这篇文章先聚焦通道层和客户端常驻服务的一个组件介绍和关键技术实现。

通道层
推送后端接入:这一层是业务的接入层,一般来说是对内网开放,通常采取的是RPC的远程调用实现,消息更高。

存储:依赖进行消息数据统计,以及离线消息信息存储,待终端网络开启之后再实施推送。

消息分发:依据消息到达进行就近机房选择,消息体封装等。

推送前端接入:和客户端进行长连接维持。

客户端
鉴权及防伪服务:进行消息体格式校验,消息防伪造验证

状态适配服务:识别当前终端所处环境和状态,例如微信所做的状态适配服务区分为:活跃态、次活跃态、自适应计算态、后台稳定态以及空闲态等几种。选择不同的状态会传送给心跳服务采取不同的心跳时间间隔。

心跳服务:为了应对NAT断连、DHCP租期失效、连接探测,需要有一个心跳服务进行维持,而心跳服务的选择策略是长连接维持的一个重要优化点。

后端感知:主要是为了应对DNS劫持以及就近流量访问所出现的一个服务。


技术点

 心跳机制优化:

前面我们已经讨论了长连接里面一个非常重要的优化点就是心跳机制优化,那位什么需要心跳机制优化,我们先看下现实场景下有什么问题会导致一定需要长连接维持及优化。长连接需要维持那么肯定是有一些原因会导致长连接失效,总结一下有如下几个场景:

NAT断连
因为 IP v4 的 IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP,手机要连接 Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断。下表列出一些已测试过的网络的NAT超时时间(更多数据由于测试条件所限没有测到):
消息推送架构介绍_第2张图片

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

安卓DHCP的租期(lease time)问题

目前测试发现安卓系统对DHCP的处理有Bug:DHCP租期到了不会主动续约并且会继续使用过期IP,详细描述见http://www.net.princeton.edu/android/android-stops-renewing-lease-keeps-using-IP-address-11236.html。这个问题导致的问题表象是,在超过租期的某个时间点(没有规律)会导致IP过期,老的TCP连接不能正常收发数据。并且系统没有网络变化事件,只有等应用判断主动建立新的TCP连接才引起安卓设备重新向DHCP Server申请IP租用。


流程优化:

基于以上两种因素的考虑,长连接就很有可能会出现断开,那么具体选择多久进行一次心跳探测呢?首先可以肯定的一点不能太久,要不能连接早已被断开或者说消息接收不及时,那太频繁的进行心跳检测呢显然也有一个问题,那就是功耗会加大,如何平衡这个选择呢?通常做法就是依据状态选择智能选择心跳时隔,这里的状态分为两类:网络状态及应用所处场景状态(例如前面所描述的微信所区分的几类状态)。

1、  按场景状态选择心跳区间

2、  通过心跳增加步长(网络质量良好)和心跳减少步长(网络质量差)来逐渐逼近网络最优心跳区间

心跳区间选择流程如下
消息推送架构介绍_第3张图片

消息重复接收问题优化:

如果消息是单次发送及反馈,那么在网络条件不好的时候很容易出现消息重复接收问题,而如何解决消息重复接收呢?

可以通过消息序列标注解决法,具体的做法是发一批带序列号的消息下发,客户端定期反馈当前接收到的消息序列,如果序列和当前发送不匹配就从反馈的序列号开始重新发送。

消息协议选择:

首先需要确认的是这个协议是一个基于4层之上的应用层协议,在4层还是采取的是TCP/IP协议。那么我们来看下这个应用层协议如何选择

文本协议:XMPP(xml)、SIP(类http协议)

这些协议有如下共同特征,可读性强、开源组件多、协议较复杂、冗余、费流量。

二进制数据协议:protocolbuffer(PB)、MQTT

这些协议的特征是:可读性差,流程可自定义(MQTT自定义限制较大)、协议简单,轻量、编解码速度快,可大量节约流量,测试可节约50%~70%的流量

所以具体那种协议的选择需要看业务场景的使用而定。
 

DNS劫持及就近流量接入:

这个就涉及我们刚才看到的客户端出现的后端感知服务的功能,一般来说可以通过IP来和后端服务建立长连接,一旦DNS劫持之后IP连接不通,那么就可以在服务端维护一个后端服务IP列表,在客户端通过hash散列的方式去随机挑选其中一个IP连接,如果发现不通更新这个IP列表信息,这个功能还可以实现流量就近接入,例如通过把所有IP测试一遍,选择连接效率最高的那个IP就行建立长连接。
 

鉴权防伪:

当一个恶意APP伪造透传消息内容体来拉起业务行为并打开恶意网站就有可能导致用户信息泄露,那么这里就有涉及一个消息防伪问题,一般做法就是通过消息MD5加签同时进行可逆加密,到达客户端之后进行反向解密校验就可防止消息伪造的行为。

你可能感兴趣的:(大机)