关于推送系统设计的一些总结与思考(一)

关于推送、mqtt、mosquitto相关的讨论可加入群:221779856

消息与通知

本文中的消息是指交给推送系统的待发送字符串;通知是指推送系统内部,通过长连接服务发送给客户端的通知字符串,它只在推送系统内部使用,对于使用推送系统的上层应用无法感知其存在;

一、 安全性

在推送系统中,安全性最受关注的是长连接的安全管控,以及数据在长连接通道上传输的安全性。举个针对长连接的安全管控的例子:有人知道你的长连接服务的IP地址和端口之后伪造大量的TCP连接,将会对长连接服务器端产生的严重的安全和性能等隐患,对于采用通用协议的长连接服务来说该问题尤为严重,例如目前很多公司都采用mqtt协议作为长连接服务,如果不对长连接的接入进行校验,则只需在客户端抓包就可以拿到长连接服务的ip、端口,并可分析出协议类型,这时就可以伪造大量的连接来挂载到长连接服务器上,进行数据收发,这将产生安全隐患并对服务器的性能产生无法估量的压力。对于长连接通上的数据传输安全性,是指在长连接中传输的数据能否被别人窃取伪造等安全性问题?

1.1 安全与效率

长连接采用TCP还是TLS?
个人认为,这个更多的是依据推送的应用场景、业务后续的发展情况来综合判断;如果应用场景中,当前和可预见的时间内,客户端的长连接数量不多,但是对数据传输的安全性要求较高,则可采用TLS;如果当前和可预见的范围内,客户端的数量无法判断,且长连接的增长量不可预估,例如,达到几千万甚至上亿的在线连接,但是对数据的安全性要求相对不是太高,则可以采用TCP连接。
在长连接服务中使用TLS会提供一个安全的传输通道,但TLS会严重影响服务端的性能,根据以往的测试情况,在移动互联网情况下,采用TLS能让长连接服务的性能降低50%~80%左右,在以往的经验中,这里影响性能的不是数据的加解密而是连接的频繁断开与建立,因为在移动互联网情况下,客户端的网络异常是一种常态,在长连接服务支持10万连接时,每秒的断线重连数量将可能达到1000多次,如果使用TLS还可能会更高,当然这些值仅作参考,它有可能会因为应用场景不同而不同。
能否有一种兼顾安全和服务端效率的方式?
个人建议是:长连接服务使用TCP连接,数据加密由长连接之上的服务来保证,即交给长连接服务的就是加密之后的数据,这样既能保证数据的安全性,又不会过多损失长连接服务本身的性能。这里要注意的是数据加解密的对称密钥的传输,可以在客户端通过https申请长连接服务地址的时候由服务端产生并交付给用户,对称密钥应该是每个客户端在每次登陆长连接服务的时候都不一样。

1.2 接入控制

个人看法是:很多长连接服务都提供用户名和密码的方式对进入的连接进行控制,在TCP传输时,这种措施几乎无法起任何作用,这些用户名或密码数据(无论是否被加密)都会被人轻易拦截获取,别人一样可以拿着你加密后的用户名和密码堂而皇之的连接到长连接服务器中。
那么,采用TCP协议的长连接服务怎样才能有效控制客户端的接入呢?
个人建议是:不要采用用户+密码的方式,而是采用客户端ID+动态密码(或session)的方式进行接入控制,即客户端每次登陆长连接服务的时候,由服务端为这个ID的客户端动态生成一个密码或者session,客户端登陆的时候要带上这个session,服务端将校验客户端的ID和session有效性以及二者是否匹配,在这种方式中,每个ID在每次登陆推送时都会动态分配不同的密码或者session。
在长连接服务中,每个连接都会对应一个唯一的的客户端ID,如果两个ID一样的连接进来,那么后进来的连接就会把前一个踢掉,总之,长连接服务为一个客户端ID只保持一个连接。假如有人抓包分析拿到了某个连接对应的ID和密码,那么它伪造的连接只能影响那一个连接,这时会出现正常的客户端和伪造的客户端进行互踢的现象,在客户端中对这种方式稍加控制即可解决,例如互踢多少次之后客户端就要重新到服务端申请新的长连接服务,这时服务端就会为这个长连接ID分配新的长连接服务和对应的连接密码。
采用客户端ID+动态密码(或session)的方式进行接入控制的方式可以让非法连接对现有推送的影响范围降低到一个连接,再结合上一节提出应用层加密措施,让非法进入的连接即便进来收到消息也无法正确解析,同时,长连接的互踢机制也能让正常连接快速检测到有别的客户端冒充自己接入了长连接服务。
在长连接集群中如何保持连接的唯一性?
在前面的描述中,单个长连接服务不会让两个具有同样ID的连接同时工作,但是如果在长连接集群环境中,同样的ID的连接接入到不同的长连接服务的时候,单靠长连接服务本身就无法做到不同“长连接服务”之间对同一个ID的互斥,此时需要集群管理层面来进行处理,例如客户端在获取长连接服务地址的时候,要检查一下当前是否已经给该ID分配了长连接服务,如果存在已分配记录,并且这个长连接还仍然在线,那么分配的时候就直接返回上次分配的那个长连接服务,同时在返回值中告知调用客户端该ID已经在线了,此时让客户端上层来处理这种互踢场景,以避免长连接服务内部产生互踢而上层应用却无感知。
伪连接的处理
这是协议的安全性,如果自定义协议的时候一定要注意这个问题,如果采用成熟的协议例如MQTT、XMPP等协议时基本不会出现这些问题。在基于TCP的长连接服务中,应用层的协议一般要求TCP三次握手之后,要进行应用层协议的初始化操作,例如,建立连接等等;以MQTT协议为例,在TCP连接建立之后,服务端从该连接收到的第一个消息一定是MQTT的连接消息。假如此时有人伪造与TCP的连接之后,什么动作都不做,这时长连接服务要确保不让这种连接参与任何的业务,并适时清理掉这些连接。

二、 消息送达率

影响推送系统送达率的因素非常多,服务端、客户端、网络这三者之间有任何一点出了问题都会影响推送消息的及时性,甚至造成消息丢失。下面将描述几种可以提升消息送达率的措施。

2.1 推送兼容性

目前很多手机厂商都定制了自己的ROM。这些五花八门的定制rom为了节电、省流量等目的,会对应用在息屏状态进行特殊处理,这时自己开发的长连接服务经常会被定制rom给挂起来或者直接被干掉。因此,无论推送系统设计的多么完美,一旦长连接的客户端被ROM这样处理了,都无法正常工作,进而造成消息无法及时推送下去的问题。
不过,很多手机厂商都自己开发了自己的推送系统,例如小米,它们的rom中会将自己的推送客户端加入到白名单中,因此,在这些定制的rom且有自己推送的厂商中,他们自己推送系统的消息送达率一般都会比其他第三方推送的消息送达率高。
针对这种情况,如果自研的推送系统能兼容这些手机厂商的rom,将能有效提升整个推送系统的消息送达率。那么如何结合?结合过程中可能会遇到哪些技术或者技术之外的问题?
个人建议,将第三方的推送当做长连接服务来使用;在推送系统内部来兼容第三方的推送,对使用推送的上层应用屏蔽掉这些内部细节。具体实现时,由客户端检测并决定使用哪个第三方推送,并将决定结果在登录推送系统时传递过去,推送系统内部要记录下这个客户端使用了哪个第三方推送系统,它在第三方推送系统中的标识是什么等信息;在推送消息给该客户端时,推送系统也将首先查询该客户端使用了哪个推送,然后再将推送的消息发送给推送客户端SDK中,推送客户端的SDK收到消息之后,再将消息交付给客户端的应用,这样整体下来,使用推送的任何业务都感知不到推送系统内部(包括推送的后台和推送的客户端SDK)的处理逻辑。
与第三方推送结合过程会遇到的问题及对策
(1)不相信第三方推送,很多公司自己开发推送系统就是因为不相信第三方推送的安全性,不愿把自己的要发送的数据交给别人来推送。在上述提出的结合方案中再加上本文提出的应用层加密的方式,第三方推送的地位就像采用了TCP连接的长连接服务,在整个系统设计中就假设它们是不安全的,在将消息交给第三方推送的时候已经进行了加密,因此可以保证数据的安全性。
(2)定制rom、自研推送的手机厂商太多,在推送系统中兼容哪些第三方推送?兼容多少个第三方推送合适呢?可以选择国内销量TOP2~TOP3的两三个手机厂商,然后将他们的推送集成到自己的推送系统中,自己开发的推送SDK中也要集成这些手机厂商的sdk。集成的第三方推送不易太多,否则推送的客户端SDK会变得很大。

2.2 提升长连接服务的送达率

在推送系统中,长连接服务主要负责给在线用户发送通知,由于移动互联网环境下,网络异常是常态,客户端频繁断线-重连都是常见的情况,因此,提升长连接服务在这些场景下的通知送达率将有效保证消息送达的及时性。那么,如何才能提升长连接服务的通知送达率呢?
在自己开发的长连接服务中,建议采用延迟释放连接对象的方法。每个客户端在长连接服务中都有唯一的ID,在开发长连接服务程序时,会对每个客户端分配一个对象,在该对象中保存了该客户端的ID,待发送给它的通知,服务端与客户端进行通信使用的socket等信息。当socket连接断开时,连接对象不应立即释放,而是要延迟一段时间再释放,在延迟释放期间,如果该对象又有新的连接进来,则直接让对象直接使用该连接,在断线期间发送给该客户端的消息都会先交给对象保存,如此一来,这些异常断线期间的消息就可以使用新的socket连接发送到客户端,从而在代码实现层面提升长连接服务的通知送达率。

你可能感兴趣的:(linux,设计模式,分布式服务,MQTT协议及其应用,架构设计,推送及IM)