后台架构剖析
1.聊天APP后台架构
1).网络特性及解决方案
a.弱网络性
弱网络性指手机不断移动(例如地铁、汽车)的特性,处于快速移动中,出现信号不稳、响应时间过长、出现丢包等情况。
因此针对弱网络环境,开发者设计协议的时候必须考虑尽量减少数据往返次数,降低耗时。
由于弱网络性,App以长连接的形式连接服务器,可能出现App和服务器连接忽然中断的情况,而且这种情况无法通过连接端口的异常判断。例如当地铁高速行驶的时候网络中断,App无法向服务器的端口发送断开的信息,服务器还以为App一直保持连接,这种现象称为TCP half-open
TCP half-open的危害:
- 占用服务器资源(服务器维持连接耗费资源)
- 发送消息的异常
有效防止TCP half-open的方法是使用应用层心跳包:在App和服务器保持连接过程中,App在规定时间间隔内向服务器发送一个数据(为节省流量,数据可以是单字节字符,因为数据是隔一定时间发送一次,这种数据被形象的称为"心跳数据")。对于未收到心跳数据的连接,服务器主动断开连接。
服务器检查App的连接有三种方式:
-
服务器记录每个连接收到心跳包的最后时间,每隔一段时间检查所有连接,若某个连接最后收到心跳包时间减去当前时间大于超时时间,断开连接。这种方式主要缺点是需要同时检查所有连接,如果连接数量过大,检查连接是对系统的性能影响大。
-
服务器为每一个连接建立一个定时器,到了规定的超时时间没有收到心跳包定时器就触发,把当前连接断开,如果收到心跳包,就重置定时器,不断重复过程。这种方法主要缺点,为每一个连接设置独立定时器,如果连接数量过大,会建立大量定时器,耗费系统资源。
-
这种方式是方式1和方式2的折中,称为时间轮片。按照超时时间的长短,每秒设置一个桶,如果超时时间为60秒就设置60个桶,60个桶组成一个循环队列,第一个桶放一秒后将要超时的连接,第二个桶放置两秒后将要超时的连接,每一个连接一收到心跳包就把自己放到第60个桶,然后每秒的定时器把第一个桶中的连接断开,把这个空桶放到队尾。这种服务器检查App的连接方式,好处是不需要检查所有的连接,节省大量的系统资源。
b.对流量敏感性
非Wi-Fi环境下用户使用手机流量上网,对流量敏感。
2).协议
a.协议要求
- 简化系统设计
- 提供可靠高效的消息传输
- 易于扩展需求
b.常用协议
- XMPP:基于XML的消息协议,曾被广泛应用于Gtalk、Facebook的聊天服务
优点:XAMP协议有大量网络资料和成熟开源模块,易于移动端集成。
缺点:耗费流量
举例子:XMPP添加好友
<iq id="rostersetl" type="set"> <query XMLns="jabber:iq:roster"> <item jid="user@jabbercn.org" name="user"/> </query> </iq> <presence from="contact@rooyee.biz" to=user@jabbercn.org type="subscribe">
- MQTT:IBM开发的聊天协议,一个简单的消息协议
- 类ActivitySync:微信实现的协议,省流量,性能高,私有协议,IM所有功能自己实现。
c.常用开源服务器
- Ejobberd:Erlang语言开发,成熟稳定,支持集群,支持多进程、并发。
- Openfire:Java语言开发,成熟稳定,插件多,但对内存要求高,并发低,集群性能弱。
d.tcp协议粘包问题及解决方案
- 问题的由来:App的通信协议一般使用TCP协议,接收端存在缓冲区。
- 问题案例:假设用户缓存区大小为15字节,当第一个数据包(10字节)和第二个数据包(10个字节)到达接收端,根据缓冲区大小取数据15字节,这时候取出数据包含第一个数据包和第二个数据包,产生粘包问题。(例如第一个数据包为"1234567890",第二个数据包为"0123456789"),取出数据为"123456789001234"
- 解决方案:制定合理的协议格式,在每个数据包中标明包的长度。
one. MySQL协议格式
MySQL协议中一个数据包的前3个字节是数据包长度,第4个字节是数据包的序列号,剩下字节是数据。
two. Redis 协议格式(Redis协议是以CR LF结尾的)
Redis协议格式如下:
*参数个数 CR LF
$第一个参数的字符串占用字节数 CR LF
参数数据 CR LF
......
$第N个参数的字符串占用字节数 CR LF
参数数据 CR LF
举例子(Redis命令:"set name jeff"):
*3\r\n$3\r\n\set\r\n\$4\r\n\name\$4\r\n\jeff\r\n
e.丢包情况处理
1.问题的由来:由于移动物联网的弱网络性,经常出现丢包的情况。 需要处理的问题:
- 怎么确定客户端是否接收消息?
- 怎么确定需要重发哪些消息?
2.解决方案:
- 基于队列的消息协议(二次握手)
传统的IM协议是基于队列的消息发送和反馈机制。
服务器按照顺序把队列中的消息依次发给客户端,当客户端收到消息后给服务端发送"确认收到"的应答,如果过了一段时间还没有收到客户端的应答,就重发该消息。
这样就存在两个问题:
one.如果客户端已收到消息且发送了"确认收到"的应答,网络中断,造成服务器收不到应答,服务器重发,导致客户端收到重复的消息。
two.每条消息都需要应答极其费时间,服务器要维护每个消息的状态也容易出错。
f.非文本文件传送
3).后台整体架构设计
a.连接层
- 作用:保持App与服务层之间的连接,把消息通过队列转发到逻辑层处理。
- 注意要点:连接层服务器要很少重启,一旦重启造成大量App断线,短时间内重连服务器。服务器一瞬间大量连接请求引发类似DDOS攻击的现象。防止这类现象出现的机制:当连接层服务器准备重启的时候,发送一条消息到连接着这台服务器的App,让其重连到别的服务器。
- 动态分配接入点方案:App访问接入点服务器,接入点服务器根据各个连接服务器的负载等因素,综合计算,返回一个连接服务器的IP给App,App通过IP连接服务器。如果App因为网络原因无法连接接入点服务器,可以预埋IP,让App连接上默认的连接服务器。
b.业务层
- 验证模块:验证用户信息
- 路由模块:连接服务器的集群,包含了数量众多的服务器,例如当用户A向用户B发送消息,两个用户的连接不一定是一个服务器,因此需要通过路由模块判断用户所在的服务器。群聊功能要在这个模块实现"订阅/发布"关系。
- 统计模块:统计各种信息,例如总连接数、每秒发送消息数、总用户数、Android客户端连接数、iOS客户端连接数等等。
- 数据存储模块:存储消息、统计信息、用户身份信息等
- 连接层通过队列向业务层进行消息传递,业务层不断从队列中取出消息进行相关的处理,使用队列后,业务层重启程序时,对用户影响最小,因为所要处理的消息存储在队列中,重启不会造成消息丢失。
c.持久层
该层提供数据存取服务,数据包含用户的身份信息、消息、统计信息等。
根据不同数据对存取速度的不同要求,可使用不同的软件存储不同的业务数据。例如用户身份信息这种高频读写的数据,可存储在Redis、Memcached等内存数据库中。
d.工作流程(以老公向老婆发送"我爱你"为例)
- 老公向连接服务器1发送消息(里面包含老婆的唯一标识和内容"我爱你")
- 连接服务器1接收该消息后向老公返回一个应答,同时队列把消息传递到业务层
- 业务层的验证模块验证老公的身份信息,验证完成后通过路由模块把消息传递给老婆所在的服务器为连接服务器2,把消息传递到连接服务器2从而发送到老婆的手机上
- 业务层把消息传递给老婆时,对相关的消息进行统计,同时数据存储模块把数据持久化
2.社交APP后台架构
1).基本表结构
2).推拉模式
a.推模式
b.拉模式
c.推拉模式总结
d.微博中的应用
3).数据库架构
a.自增id
b.分表分库策略
4).缓存架构
a.分布式缓存
b.主从缓存
c.防止缓存失效措施
3.LBS APP后台架构
1).地理坐标处理及坐标系认识
2).查找附件的人功能
a.MySql空间数据库
b.geohash编码
c.MongoDB地理位置查询功能
3).基于MongoDB的LBS后台架构
a.副本集架构
b.分片架构
4.推送服务器后台架构
1). Android推送 (类似Gopush-Cluster架构)
a.App连接推送服务器的流程
b.后台推送消息到App的流程
c.App获取离线消息的流程
2).iOS推送(基于APNS)
a.APNS原理
b.APNS推送协议分析
c.iOS推送服务器架构
未完待续
引用自:
https://github.com/zqmath1994/Note/blob/master/%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/%E5%A4%A7%E5%AD%A6%E5%90%8E%E5%8F%B0%E5%BC%80%E5%8F%91%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E6%95%B4%E7%90%86/%E5%90%8E%E5%8F%B0%E6%9E%B6%E6%9E%84%E5%89%96%E6%9E%90.md