如何实现消息推送系统

1、前言

     随着IOS,Android应用的普及,对推送消息的需求也越来来重要,目前市面上有的个推推送、极光推送、友盟推送等等,那如何自己搭建一个推荐系统昵?

2、名词说明:

     设备端:消息接收方;

     消息服务:负责和设备端建立连接,并推送消息给设备端

     消息系统: 消息服务集群;

     路由系统:根据消息服务的负责返回合适的消息服务给设备端(主要负责监控当前可用的消息服务、监控当前消费服务的负载情况,返回合适的消息服务给设备端)

     业务系统:消息系统之外的其它业务系统;

     平台:包括设备端和前端页面系统;

3、设计原则

      可扩展性:为了保证系统的高可用,要求各模块都可以水平扩展;

      高可用性:设计过程中必须考虑系统的稳定性;

      原则单一:各组件的功能的单一(如:消息系统只负责和设备端连接和向设备端发送消息,禁止在消息系统中做复杂的业务逻辑)     

4、技术选型

     2.1:传输方式

     要满足大量的连接数、同时支持双全工通信,并且性能也得有保障。

     在 Java 技术栈中进行选型首先自然是排除掉了传统 IO

     那就只有选 NIO 了,在这个层面其实选择也不多,考虑到社区、资料维护等方面最终选择了 Netty。

     2.2:数据格式选型

         1、json 简单

         2、hession(为了简单说明,该文档就以hession为主)

         3、protobuf

         4、自定义协议

5、架构图

     

 

     

 

说明:

    5.1、服务端启动说明(上面红色箭头标记)

            

           启动包括两部分

           1)注册服务到zk中;

           2)监听MQ消息中,本机到消息。   

    5.2、客户端启动流程

                     

         

         客户端启动包括五部分

           1)请求路由系统获取可用到消息服务;

           2)路由系统获取可用到消息服务;

           3)路由系统通过redis获取可用消息服务的负载,路由系统根据消息服务的负载返回合适的消息服务给设备端;

           4)设备端连接消息服务;

           5)消息服务接受连接服务,进行验证消息,向Redis中记录连接信息。

        

     5.3、启动过程中需要考虑的几个问题

            1)保证连接的稳定可靠(心跳)设备端和消息服务都进行必要的心跳检查。

当一台智能手机连上移动网络时,其实并没有真正连接上Internet,运营商分配给手机的IP其实是运营商的内网IP,手机终端要连接上Internet还必须通过运营商的网关进行IP地址的转换,这个网关简称为NAT(NetWork Address Translation),简单来说就是手机终端连接Internet 其实就是移动内网IP,端口,外网IP之间相互映射。

GGSN(GateWay GPRS Support Note)模块就实现了NAT功能,由于大部分的移动无线网络运营商为了减少网关NAT映射表的负荷,如果一个链路有一段时间没有通信时就会删除其对应表,造成链路中断,正是这种刻意缩短空闲连接的释放超时,原本是想节省信道资源的作用,没想到让互联网的应用不得以远高于正常频率发送心跳来维护推送的长连接。以中移动的2.5G网络为例,大约5分钟左右的基带空闲,连接就会被释放。

由于移动无线网络的特点,推送服务的心跳周期并不能设置的太长,否则长连接会被释放,造成频繁的客户端重连,但是也不能设置太短,否则在当前缺乏统一心跳框架的机制下很容易导致信令风暴(例如微信心跳信令风暴问题)。具体的心跳周期并没有统一的标准,180S也许是个不错的选择,微信为300S,本系统选择120s

 

 

                设备端心跳检查流程

                 

                消费服务心跳流程:

                

             说明:设备端和消息服务心跳过程中可能存在几个问题

             1、消息不停端断开和重连

                  原因:设备端和消息服务相互断开重连;

                  解决方法:在redis中记录设备新建连接时间,如果连接是新建的,消费服务心跳次数检查调整为12次。

             2、在redis中通一个客户端对应多个消费服务

                   原因:redis中消费连接信息按5分钟过期,心跳120s中进行一次,有可能断开连接没有清理完成前,设备端已经连接到另一个消费服务上了;

                   解决办法:在连接信息中记录最新更新信息,以最新更新信息到设备端为准(如果redis记录信息Key能定义唯一,不存在此问题)。

             3、消息服务和redis中连接信息不一致

                   原因:由于不可预知原因

                   解决办法:  redis中消费连接信息过期时间为5分钟。

                                       情况1:5分钟之后,消费服务客户端不会把消息推送到挂掉的消息服务服务器。

                                       情况2:  5分钟以内的消息,

                                                   1、如果消息系统挂起,说明连接已经断开,设备端会重新建立连接,在建立连接时,设备端传入上次最后一条消息,系统推送上次最后一条消息之后的所有消息给设备端;

                                                    同时,设备端接受到消息,根据消息发送到时间和消息id进行去重和重排序,保证消息到唯一和顺序性;

                                                   2、消息服务如果收到本地连接不存在的消息,重新推送消息到MQ中;

 

             4、连接建立和连接断开的业务处理逻辑和消息系统耦合

                   原因:连接建立和连接断开 只有消息系统清楚;

                   解决办法:消息系统建立连接和断开连接发送消息给MQ,业务系统监控MQ,进行业务处理。

             5、消息系统挂机,redis中包含连接信息;

                    解决办法:监听zk中节点事件,当节点下线之后,同时删除redis中连接信息

5.4、发送消息 

     发送消息流程图:

     

    说明:

    平台:包括设备端,页面,各种第三方服务。

    步骤:

    1、平台根据业务需求请求业务系统(如:手机向电视发送消息,发起视频通话) 

    2、业务系统处理完业务逻辑,调用消息服务客户端;

    3、消息服务客户端处理消息

         3.1、获取消息应该发送到哪个topic下(如果获取不到相应到topic只写数据(可能设备端已经下线),不写MQ);

         3.2、保存消息到mysql数据库中(只有消息写入数据库成功才返回平台消息发送成功);

         3.3、向MQ中写入消息;

    4、消费服务监听MQ消息,推送消息给设备端 (如果消息不在当前线程中,则重新用消息服务客户端 发送到MQ中,如果连续发送3次都没找到设备端,(设备端已下线)直接丢弃消息),

          回写消息状态到mysql数据库

6、 监控系统   

      监控指标:1、各服务消息的系统性能;2、各服务消息的连接数;3、各服务消息的推送消息数据和推送的消息结果,4、推送消息日志(可以用log4j接入elk) ,5、路由系统的监控;             

 

posted @ 2019-07-09 11:52  微笑不加冰  阅读(4333)  评论(0编辑  收藏  举报