Push or Pull?
采用Pull模型还是Push模型是很多中间件都会面临的一个问题。消息中间件、配置管理中心等都会需要考虑Client和Server之间的交互采用哪种模型:
服务端主动推送数据给客户端?
客户端主动从服务端拉取数据?
本篇文章对比Pull和Push,结合消息中间件的场景进一步探讨有没有其他更合适的模型。
Push VS Pull
1. Push
Push即服务端主动发送数据给客户端。在服务端收到消息之后立即推送给客户端。
Push模型最大的好处就是实时性。因为服务端可以做到只要有消息就立即推送,所以消息的消费没有“额外”的延迟。
但是Push模式在消息中间件的场景中会面临以下一些问题:
在Broker端需要维护Consumer的状态,不利于Broker去支持大量的Consumer的场景
Consumer的消费速度是不一致的,由Broker进行推送难以处理不同的Consumer的状况
Broker难以处理Consumer无法消费消息的情况(Broker无法确定Consumer的故障是短暂的还是永久的)
大量的推送消息会加重Consumer的负载或者冲垮Consumer
Pull模式可以很好的应对以上的这些场景。
2.Pull
Pull模式由Consumer主动从Broker获取消息。
这样带来了一些好处:
Broker不再需要维护Consumer的状态(每一次pull都包含了其实偏移量等必要的信息)
状态维护在Consumer,所以Consumer可以很容易的根据自身的负载等状态来决定从Broker获取消息的频率
Pull模式还有一个好处是可以聚合消息。
因为Broker无法预测写一条消息产生的时间,所以在收到消息之后只能立即推送给Consumer,所以无法对消息聚合后再推送给Consumer。 而Pull模式由Consumer主动来获取消息,每一次Pull时都尽可能多的获取已近在Broker上的消息。
但是,和Push模式正好相反,Pull就面临了实时性的问题。
因为由Consumer主动来Pull消息,所以实时性和Pull的周期相关,这里就产生了“额外”延迟。如果为了降低延迟来提升Pull的执行频率,可能在没有消息的时候产生大量的Pull请求(消息中间件是完全解耦的,Broker和Consumer无法预测下一条消息在什么时候产生);如果频率低了,那延迟自然就大了。
另外,Pull模式状态维护在Consumer,所以多个Consumer之间需要相互协调,这里就需要引入ZK或者自己实现NameServer之类的服务来完成Consumer之间的协调。
有没有一种方式,能结合Push和Pull的优势,同时变各自的缺陷呢?答案是肯定的。
Long-Polling
使用long-polling模式,Consumer主动发起请求到Broker,正常情况下Broker响应消息给Consumer;在没有消息或者其他一些特殊场景下,可以将请求阻塞在服务端延迟返回。
long-polling不是一种Push模式,而是Pull的一个变种。
那么:
在Broker一直有可读消息的情况下,long-polling就等价于执行间隔为0的pull模式(每次收到Pull结果就发起下一次Pull请求)。
在Broker没有可读消息的情况下,请求阻塞在了Broker,在产生下一条消息或者请求“超时之前”响应请求给Consumer。
以上两点避免了多余的Pull请求,同时也解决Pull请求的执行频率导致的“额外”的延迟。
注意上面有一个概念:“超时之前”。每一个请求都有超时时间,Pull请求也是。“超时之前”的含义是在Consumer的“Pull”请求超时之前。
基于long-polling的模型,Broker需要保证在请求超时之前返回一个结果给Consumer,无论这个结果是读取到了消息或者没有可读消息。
因为Consumer和Broker之间的时间是有偏差的,且请求从Consumer发送到Broker也是需要时间的,所以如果一个请求的超时时间是5秒,而这个请求在Broker端阻塞了5秒才返回,那么Consumer在收到Broker响应之前就会判定请求超时。所以Broker需要保证在Consumer判定请求超时之前返回一个结果。
通常的做法时在Broker端可以阻塞请求的时间总是小于long-polling请求的超时时间。比如long-polling请求的超时时间为30秒,那么Broker在收到请求后最迟在25s之后一定会返回一个结果。中间5s的差值来应对Broker和Consumer的始终存在偏差和网络存在延迟的情况。 (可见Long-Polling模式的前提是Broker和Consumer之间的时间偏差没有“很大”)
Long-Polling还存在什么问题吗,还能改进吗?
Dynamic Push/Pull
“在Broker一直有可读消息的情况下,long-polling就等价于执行间隔为0的pull模式(每次收到Pull结果就发起下一次Pull请求)。”
这是上面long-polling在服务端一直有可消费消息的处理情况。在这个情况下,一条消息如果在long-polling请求返回时到达服务端,那么它被Consumer消费到的延迟是:
假设Broker和Consumer之间的一次网络开销时间为R毫秒,
那么这条消息需要经历3R才能到达Consumer
第一个R:消息已经到达Broker,但是long-polling请求已经读完数据准备返回Consumer,从Broker到Consumer消耗了R
第二个R:Consumer收到了Broker的响应,发起下一次long-polling,这个请求到达Broker需要一个R
的时间
第三个R:Broker收到请求读取了这条数据,那么返回到Consumer需要一个R的时间
所以总共需要3R(不考虑读取的开销,只考虑网络开销)
另外,在这种情况下Broker和Consumer之间一直在进行请求和响应(long-polling变成了间隔为0的pull)。
考虑这样一种方式,它有long-polling的优势,同时能减少在有消息可读的情况下由Broker主动push消息给Consumer,减少不必要的请求。
消息中间件的Consumer实现
在消息中间件的Consumer中会有一个Buffer来缓存从Broker获取的消息,而用户的消费线程从这个Buffer中获取消费来消息,获取消息的线程和消费线程通过这个Buffer进行数据传递。
pull线程从服务端获取数据,然后写入到Buffer
consume线程从Buffer获取消息进行消费
有这个Buffer的存在,是否可以在long-polling请求时将Buffer剩余空间告知给Broker,由Broker负责推送数据。此时Broker知道最多可以推送多少条数据,那么就可以控制推送行为,不至于冲垮Consumer。
上面这幅图是akka的Dynamic Push/Pull示意图,思路就是每次请求会带上本地当前可以接收的数据的容量,这样在一段时间内可以由Server端主动推送消息给请求方,避免过多的请求。
akka的Dynamic Push/Pull模型非常适合应用到Consumer获取消息的场景。
Broker端对Dynamic Push/Pull的处理流程大致如下:
收到long-polling请求
while(有数据可以消费&请求没超时&Buffer还有容量) {
读取一批消息
Push到Consumer
Buffer-PushedAmount 即减少Buffer容量
}
response long-polling请求
结束(等待下一个long-polling再次开始这个流程)
Consumer端对Dynamic Push/Pull的处理流程大致如下:
收到Broker的响应:
if (long-polling的response) {
将获取的消息写入Buffer
获取Buffer的剩余容量和其他状态
发起新的long-polling请求
} else {
// Dynamic Push/Pull的推送结果
将获取的消息写入到Buffer(不发起新的请求)
}
举个例子:
Consumer发起请求时Buffer剩余容量为100,Broker每次最多返回32条消息,那么Consumer的这次long-polling请求Broker将在执行3次push(共push96条消息)之后返回response给Consumer(response包含4条消息)。
如果采用long-polling模型,Consumer每发送一次请求Broker执行一次响应,这个例子需要进行4次long-polling交互(共4个request和4个response,8次网络操作;Dynamic Push/Pull中是1个request,三次push和一个response,共5次网络操作)。
总结:
Dynamic Push/Pull的模型利用了Consumer本地Buffer的容量作为一次long-polling最多可以返回的数据量,相对于long-polling模型减少了Consumer发起请求的次数,同时减少了不必要的延迟(连续的Push之间没有延迟,一批消息到Consumer的延迟就是一个网络开销;long-polling最大会是3个网络开销)。
Dynamic Push/Pull还有一些需要考虑的问题,比如连续推送的顺序性保证,如果丢包了怎么处理之类的问题,有兴趣可以自己考虑一下(也可以私下交流)。
结语
本篇内容比较了Push、Poll、Long-Polling、Dynamic Push/Pull模型。
Push模型实时性好,但是因为状态维护等问题,难以应用到消息中间件的实践中。
Pull模式实现起来会相对简单一些,但是实时性取决于轮训的频率,在对实时性要求高的场景不适合使用。
Long-Polling结合了Push和Pull各自的优势,在Pull的基础上保证了实时性,实现也不会非常复杂,是比较常用的一种实现方案。
Dynamic Push/Pull在Long-Polling的基础上,进一步优化,减少更多不必要的请求。但是先对实现起来会复杂一些,需要处理更多的异常情况。
参考内容:Google->Reactive Stream Processing with Akka Streams
往期文章:
Push or Pull?的更多相关文章
- ZeroMQ之Push与Pull (Java)
本系列文章均转自:http://blog.csdn.net/kobejayandy/article/details/20163431 在ZeroMQ中并没有绝对的服务端与客户端之分,所有的数据接收与发 ...
- Git的纯命令操作,Install,Clone , Commit,Push,Pull,版本回退,撤销更新,分支的创建/切换/更新/提交/合并,代码冲突
Git的纯命令操作,Install,Clone , Commit,Push,Pull,版本回退,撤销更新,分支的创建/切换/更新/提交/合并,代码冲突 这篇是接着上篇分布式版本库--Windows下G ...
- Nginx-rtmp模块实现流媒体play、push、pull功能
官方wiki:https://github.com/arut/nginx-rtmp-module#readme Nginx rtmp 功能特点 1. 支持音视频直播 2. 支持flv/mp4视 ...
- 【Bootstrapt】offset、push、pull
实现方式的区别: col-md-offset-*,是利用margin-left实现的,col-md-push-*/col-md-pull-*是利用相对定位实现的. 效果的区别: col-md-offs ...
- git push和pull如何解决冲突!!!精品
多人合作完成项目时,git push 和 pull经常会发生冲突,根本原因就是远程的东西和本地的东西长的不一样,以下步骤能完美解决所有冲突!(先查看一下分支(git branch),确认没错再进行下面 ...
- oschina代码仓库远程push,pull免密实操总结
刚做项目,用到开源中国(oschina)的git仓库,一个多月一直在痛苦的反复输密码的过程中度过.中间配置过几次免密登录,但总是时而登的上去,时而不行,大多数情况不行.近几日项目做完了,正好有空把这个 ...
- adb push和pull使用
1.运行cmd> 进入adb.exe目录 2.>adb connect ip 3.>adb remount 4.>adb push 本地apk全路径 /system/app/ ...
- git向gitHub上push和pull数据.
1.在gitHub上首先建立仓储.这个过程就不在啰嗦了. 2.注意上图中右下角的https,ssh等东西. 3.向git上传的工具特别多.我这里用的cygwin. 至于cygwin自己到网上去下载.安 ...
- 02_创建Git仓库,克隆仓库,git add,git commit,git push,git pull,同行冲突,不同行冲突的结局方案,git mergetool的使用
1 创建Git资源库,残酷目录信息 创建git资源库的命令: git init –bare 仓库名称 (其中-bare表示的意思是空的库的意思) 进入E:\software\repository\gi ...
随机推荐
- IMPEX
1.Impex是基于java Model的一种面向对象的数据操作手段,因此写impex代码前需要理清java Model之间的依赖关系. 2.基本语法:mode type[modifier=value ...
- java中的各种数据类型在内存中存储的方式
原文地址:http://blog.csdn.net/aaa1117a8w5s6d/article/details/8251456 1.Java是如何管理内存的 java的内存管理就是对象的分配和释放问 ...
- log4j实时将数据写入到kafka,Demo和相关的配置详解
一:在项目中引入对应的JAR包,如下,注意对应的包与之前包的冲突 <dependencies> <dependency> <groupId>junit</gr ...
- 轻松学JVM(一)——基本原理
前言 JVM一直是java知识里面进阶阶段的重要部分,如果希望在java领域研究的更深入,则jvm则是如论如何也避开不了的话题,本系列试图通过简洁易读的方式,讲解jvm必要的知识点. 运行流程 我们都 ...
- MyEclipse Web 项目导入 Eclipse 中需要改的文件
来自为知笔记(Wiz)
- 《Java从入门到放弃》入门篇:springMVC数据传递
springMVC中的数据传递方式与JSP和Struts2相比,更加的简单.具体有什么样的区别呢?我们通过下面这张图来对比就知道了. 随手画的,有些错别字,不用太在意..... 接下来,进入正题,sp ...
- HDU 1176 免费馅饼:dp
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1176 题意: 横坐标范围为[0,10],你在第0秒站在坐标为5的地方. 在接下来的一段时间内,会有n个 ...
- SK-Learn 全家福
SK-Learn API 全家福 最近SK-Learn用的比较多, 以后也会经常用,将Sk-Learn 所有内容整理了一下,整理思路,并可以备查. (高清图片可以用鼠标右键在单独窗口打开,或者保存到本 ...
- 使用EasyWechat快速开发微信支付
前期准备: 申请微信支付后, 会收到2个参数, 商户id,和商户key.注意,这2个参数,不要和微信的参数混淆.微信参数: appid, appkey, token支付参数: merchant_id( ...
- oracle内存优化
一直都想总结一下oracle内存调整方面的知识,最近正好优化一个数据库内存参数,查找一些资料并且google很多下.现在记录下来,做下备份. 一.概述: ...