App后端设计方案

一、网络连接层

连接层负责的功能:1.与APP保持连接; 2.把请求消息通过消息队列转发到业务层处理

连接层在集群部署的情况下,采取动态分配接入点的方式,方案如下:

1.App访问接入点服务器,接入点服务器根据负载等因素综合计算,返回一个连接服务器的IP给APP,然后APP通过这个IP连接到服务器。


app连接服务器

2.如果因为DNS故障等原因无法通过接入点服务器获取IP,可以使用预埋IP的方案:在APP中有一个默认的连接服务器IP,当APP由于DNS问题等原因无法通过接入点服务器获取IP就连接默认的IP。

访问接入点服务器失败的情况


弱网性

由于App处于不断的移动中,有可能在快速移动当中,出现信号不稳定,经常丢包的情况。针对弱网性,app以长连接的方式到服务器,但是可能连接中断,出现TCP half-open的情况。

TCP half-open的危害:

    1.占用服务器资源

    2.网络恢复后出现业务方面的异常

为保证连接在弱网环境下的可用性,采取时间轮片的方式:

       按照超时时间的长短,每秒设置一个桶,如果超时时间为60秒就设置60个桶,60个桶组成一个循环队列。第一个桶放一秒后将要超时的连接,第二个桶方两秒后将要超时的连接,每个连接已收到心跳包就把自己放在第60个桶,然后在每秒的定时器中把第一个桶的所有连接断开,把这个空桶放在队尾。


时间轮片连接模型

二、协议

通信协议:http

报文协议:

公共参数:

        _mt:所要调用的API的名称,多个API之间用半角逗号隔开,例如 /user/get

        _aid:所属APP的编号,一个预定义的正整数值

        _tk:当前客户端所持有的能够代表当前用户身份的token,在登录时由后端用户系统签发。匿名API无需提供

        _sm:对请求签名时所选用的算法。

        _sig:对请求文本执行_sm指定的签名算法后得到的签名,Base64格式。

业务参数:业务参数为某一个API提供具体参数,参数名需要和具体业务API定义的参数名一致,请求参数可以拼接到请求到URL中作为query string,也可以作为请求的body传递。

实际用例:

        URL Query String:

                URL:    http://xxxxx?_mt=product.getProductInfo&id=1&_aid=1&_sm=md5&_sig=signature

                METHOD: GET

        REQUEST BODY:

                URL:    http://xxxxx

                BODY:  _mt=product.getProductInfo&id=1&_aid=1&_sm=md5&_sig=signature

                METHOD: POST


加密协议

前端对请求进行签名,并将签名的结果连同请求参数一起传输给网关,网关使用相同的过程进行同样的签名,然后和前端的签名结果进行对比。  前端可以从所支持的签名算法列表中选择一款签名算法。

对匿名请求支持的签名算法列表:

            MD5

            SHA1 (默认)

对非匿名请求支持的签名算法列表:

            RSA

            MD5

            SHA1

            ECC (默认)

签名计算过程如下:

            1.设此次请求的所有参数(除正在计算的_sig之外)为parameters:parameters ={name: name1, value: value1}

            2.按照参数名的自然(Unicode)顺序排列对parameters进行生序排序:sortedParameters = parameters.sortBy(p -> p.name)

            3.将每个参数的参数名和参数值用等于号连接起来,然后将这些字符串直接拼接起来:requestParametersText = sortedParameters.map(p -> p.name + '=' + p.value).join()

            4.确定签名用到的盐。由于用到的算法和加密过程都是公开的,第三方完全可以模拟签名过程,使整个签名验签系统失效, 所以需要一个特殊的字符串对签名内容进行混淆,这个字符串就是                 所谓的"盐"(salt)。

                    在匿名API请求中,我们目前无法区分真实前端请求和第三方伪造请求,所以统一使用了一个固定的字符串作为盐, 即静态盐。前端所使用的静态盐必须和网关使用的一致,否则会校验不通过。

                    在非匿名API请求中,前端需要用到一个随机的唯一盐,以此来防止第三方伪造,即动态盐。为了保证前端使用的动态盐和网关一致, 并且避免该动态盐在网络间多次传输而泄漏,所以  我们采用如下方式来颁发动态盐:

                    登录或注册设备时,用户系统需要生成一个随机的字符串,即动态盐,作为登录凭据(user token)的一部分签发给前端, 并一并将该动态盐返回

                    前端在收到登录成功的返回值时,保存得到的登录凭据(user token),并将用户系统返回的动态盐(csrf token)保存到本地

                    后续每一次非匿名请求,都使用csrf token作为此次请求签名的盐

                    注意:决定使用静态盐还是动态盐的唯一判断标准是此次请求是匿名请求还是非匿名请求。 详细来说就是,如果此次请求的所有API都是匿名API,则使用静态盐,否则使用动态盐

将第三步得到的requestParametersText和第四步得到的salt拼接到一起,得到最终的签名输入文本:signatureInputText = requestParametersText + salt

选择一种签名算法(参照通用参数_sm),并对上一步得到对signatureInputText进行签名:_sig = md5(signatureInputText) // if _ms == md5


压缩协议(gzip)

如果需要进行请求压缩,setHeader("Accept-Encoding","gzip"),该情况下服务端收到请求后会进行解压缩,并在响应的header设置setHeader("Content-Encoding","gzip")

三、消息push

1.安卓推送

采用Gopush-Cluster,其架构图:


gopush-cluster架构图


消息流图

主要分为三个模块来开发,comet/web/message。

comet

主要负责消息排队、消息推送以及和客户端的连接维护;整套系统依据是消息ID顺序原则获取消息(客户端本地获取最大的消息是1,那么之后获取的消息就是大于1的,获取离线消息的时候也要从上次最大消息ID来获取),因此消息推送以后需要在comet中排队然后发起RPC给message实现存储。

message 

主要负责消息的存储和读写;接受来自comet模块的消息进行持久化,或者接受web模块的读取消息请求获取离线消息。message是可以部署多个节点来负载来自大量comet的推送压力,比如不同的comet使用不同的message节点(节点无状态),之后在comet也会做多message节点的负载均衡RPC调用和故障转移(TODO)。

web

主要负责节点询问,以及离线消息获取和后台节点管理等;节点询问主要依据客户端的订阅Key一致性hash计算出连接的comet节点地址,因此海量客户端连接的时候可以打散到多个comet来服务;另外离线消息也是通过web接口返回给客户端的,但是消息的读取是通过RPC发送给message模块的,尽量的职责单一。web节点因为无状态,可以部署多个web,来实现负载均衡和故障转移。

thrid-part

消息存储现在主要依赖Redis进行消息读写(Sorted Set),因为Sorted Set不支持int64类型的Score(我们也开发了一个支持int64 Score的分支但是因为时间原因没有大量测试上线),之后可能会使用HBase集群代替Redis的使用,已提供更安全的离线消息存储(TODO)。

另外使用了Zookeeper来实现的同一个comet的故障转移,例如comet 节点1可以有一个备选节点节点2,当节点1注册到zookeeper之后,因为机器或者其他原因宕了,这时候会在web层触发zookeeper的选举选中节点2来服务。


2.IOS推送

        苹果的推送服务通知是由自己专门的推送服务器APNs (Apple Push Notification service)来完成的,其过程是 APNs 接收到我们自己的应用服务器发出的被推送的消息,将这条消息推送到指定的 iOS 的设备上,然后再由 iOS设备通知到我们的应用程序,我们将会以通知或者声音的形式收到推送回来的消息。 iOS 远程推送的前提是,装有我们应用程序的 iOS 设备,需要向 APNs 服务器注册,注册成功后,APNs 服务器将会给我们返回一个 devicetoken,我们获取到这个 token 后会将这个 token 发送给我们自己的应用服务器。当我们需要推送消息时,我们的应用服务器将消息按照指定的格式进行打包,然后结合 iOS 设备的 devicetoken 一起发给 APNs 服务器。我们的应用会和 APNs 服务器维持一个基于 TCP 的长连接,APNs 服务器将新消息推送到iOS 设备上,然后在设备屏幕上显示出推送的消息。

设备注册APNs的流程图:

设备注册

1.Device(设备)连接APNs服务器并携带设备序列号(UUID)

2.连接成功,APNs经过打包和处理产生devicetoken并返回给注册的Device(设备)

3.Device(设备)携带获取的devicetoken发送到我们自己的应用服务器

4.完成需要被推送的Device(设备)在APNs服务器和我们自己的应用服务器的注册


推送流程图:

推送流程

1.设备安装了具有推送功能的应用,设备在有网络的情况下会连接APNs推送服务器,连接过程中,APNS 服务器会验证devicetoken,连接成功后维持一个基于TCP 的长连接;

2.Provider(我们自己的应用服务器)收到需要被推送的消息并结合被推送的 iOS设备的devicetoken一起打包发送给APNS服务器;

3.APNS服务器将推送信息推送给指定devicetoken的iOS设备;

4.iOS设备收到推送消息后通知我们的应用程序并显示和提示用户

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容