推送服务的两种方式

push模式:

一种是tcp长连接的情况下:服务器主动向客户端推送消息;由于网络异常断开时,服务器得不到通知,所以如果只是靠心跳包的机制来判断是否在线,会导致不太实时,所以客户端收到消息后,再向服务器返回ack;如果没有ack,由于现在移动客户端网络非常不稳定,所以很容易丢包;

pull模式:

pull模式时,也可以用tcp长连接的方式,它也可以循环发tcp消息去请求内容(这样看起来比tcp的push机制浪费,但是由于pull时可以带上seq,这样在得到消息时,可以不用ack了,所以tcp长连接时,pull和push性能都差不多);

另一种是http+轮询,当没有消息时,服务器hold住不返回,超时后返回;客户端再次发起请求,etcd的watch机制就是这样的方式;当然如果得到消息后,也要再给服务器一个ack请求来标记;这种方式和tcp长连接一样,也要ack,不然异常断开后,服务器也收不到通知,会导致服务器判断成没有断开而返回(也可以通过请求时带上seq让客户端自己记录已经处理的消息而不用ack);

pull模式下不用ack的方式:

ack主要是为了防止消息丢失,但是如果pull时,带上客户端最近处理消息的序列号,服务器再根据这个序列号来判断有没有新的消息需要返回(kafka是这样的方式)。这种方式需要客户端自己去记录已经处理的最后消息序列(也可以不用去记录,因为第二次读的时候会带上上次的seq,这和ack作用一样了,服务器只要在第二次读的时候根据seq把上次的消息设置成已读就好了);

关于客户端要不要把最后已经读到的消息的seq保存到本地:

不管是在push和pull模式下,都有可能是在读到消息之后,还没有来得及发ack或者第二次pull请求时,应用退出,这样的情况下,由于服务器没有把最后一条消息设置成已读,下次服务器会重发这条消息;但是这样的机率非常小,因为一般情况下,我们读到一条消息之后,会马上进行ack或者下一次的消息读取;所以在这两者之间应用退出的机率很小;当然在pull模式下如果是当一条消息读到之后,然后处理消息,最后再去读下一条消息,这样机率会很大,因为处理消息可能会导致应用崩溃;

所以如果是pull模式,最好是在应用本地记录一个最后的seq,这样,除非在刚才把最后一条消息读到,还没有进行第二次读时,就把应用删除,然后本地记录的seq也一起被删除了,这样的情况下才可能重发最后一次消息;不过这种情况下,就和中彩票的机率一样了;如果还可以把seq数据记录到除了应用之外的sdcard上做备份,这样基本上就杜绝了消息的重发了

对于客户端来说,现在主流的是tcp+push的方式,但是用tcp+pull,http+pull的方式也没有问题;只是说如果消息很频繁,用tcp+push或者tcp+pull的消息更好,不然http每次都要进行三次握手重连,也不是太方便;而对于tcp+push和tcp+pull的方式的选择,个人觉得tcp+pull更好,因为tcp+push时,服务器要去判断客户端是否在线,也就是说要去维护所有客户的在线的一个列表,然后服务器根据去循环遍历用户列表进行推送,对于移动应用来说,经常断会断线,如果用户量大,这个列表本可能就一直处在一个不稳定的状态,也不方便去遍历(可以在每次遍历之前,做一个拷贝,然后遍历这个拷贝的列表);而tcp+pull的方式,服务器就不用了去维护这个状态,只要接收到一个pull请求时,hold住,和http的处理一样,这种方式处理起来更加简单;

对于服务器性能因素来说,pull方式就得优化了,比如说是http+pull的方式,那就算服务器hold住5s,在这5s内,服务器是去循环查询是否有消息,还是怎么处理?并且每5s就断开重连,如果对于海量用户,那光是重连,还要消耗不少资源;

如果是服务器循环查询,如果同时有50w用户的连接被服务器hold住,那么就算是每秒去查询一次,一秒也有50w次的查询;性能要求太高了,就算是redis也只有10w+的读取性能(性能可由redis-benchmark进行测试,按用户id来划分,多部署几台);所以还是得像push模式一样,当是push模式时,由于有消息时就直接推了,所以不会去轮询,使用资源会非常少。

所以如果推送服务是用来应对海量用户,那么整体考虑,最好是用tcp+push的方式;如果用户很少,那么pull更简单;

如果是pull+push的模式:

当用户刚连接上时,服务器主动查询一次消息;

当用户一直连接上时,一方面客户端可以定时pull,比如5秒;另一方面服务器有消息时,主动push一个通知,让客户端尽快主动pull;

这样就结合了

原文链接:,转发请注明来源!

发表评论