Push or Pull: RocketMQ长轮询原理及源码分析

RocketMQ的长轮询机制结合了Push和Pull模式的优点,解决了实时性和资源效率的问题。在没有消息时,请求会在Broker端阻塞,直到有新消息或超时才返回,减少了不必要的请求,提高了实时性。同时,通过监听消息到达事件,实现了近实时的消息处理。
摘要由CSDN通过智能技术生成

1. Push vs Pull

Push即服务端主动发送数据给客户端。在服务端收到消息之后立即推送给客户端。Push模型最大的好处就是实时性。因为服务端可以做到只要有消息就立即推送,所以消息的消费没有“额外”的延迟。但是Push模式在消息中间件的场景中会面临以下一些问题
1)在Broker端需要维护Consumer的状态,不利于Broker去支持大量的Consumer的场景;
2)Consumer的消费速度是不一致的,由Broker进行推送难以处理不同的Consumer的状况;
3)Broker难以处理Consumer无法消费消息的情况(Broker无法确定Consumer的故障是短暂的还是永久的);
4)大量的推送消息会加重Consumer的负载或者冲垮Consumer

Pull模式由Consumer主动从Broker获取消息。其优点为:
1)Broker不再需要维护Consumer的状态(每一次pull都包含了其实偏移量等必要的信息);
2)状态维护在Consumer,所以Consumer可以很容易的根据自身的负载等状态来决定从Broker获取消息的频率。
3)因为Broker无法预测写一条消息产生的时间,所以在收到消息之后只能立即推送给Consumer,所以无法对消息聚合后再推送给Consumer。 而Pull模式由Consumer主动来获取消息,每一次Pull时都尽可能多的获取已近在Broker上的消息。
但是,和Push模式正好相反,Pull就面临了实时性的问题。因为由Consumer主动来Pull消息,所以实时性和Pull的周期相关,这里就产生了“额外”延迟。如果为了降低延迟来提升Pull的执行频率,可能在没有消息的时候产生大量的Pull请求(消息中间件是完全解耦的,Broker和Consumer无法预测下一条消息在什么时候产生);如果频率低了,那延迟自然就大了。

有没有一种方式,能结合Push和Pull的优势,同时变各自的缺陷呢?答案是肯定的。

2. RocketMQ 长轮询(long polling)

2.1 长轮询介绍

使用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之间的时间偏差没有“很大”)

2.2 RocketMQ长轮询实现源码分析

相关核心类:

  • PullMessageService:consumer端消息拉取线程,核心方法pullMessage(final PullRequest pullRequest)
  • PullMessageProcessor:broker端消息拉取处理线程,核心方法RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request)
  • PullRequestHoldService:broker端消息拉取请求hold线程,核心方法void suspendPullRequest(final String topic, final int queueId, final PullRequest pullRequest),void checkHoldRequest(),run()
  • NotifyMessageArrivingListener:broker端消息到达监听器,核心方法void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime, byte[] filterBitMap, Map<String, String> properties)
  • DefaultMessageStore#ReputMessageService:broker端消息分发线程,核心方法void doReput()

PullMessageService类的pullMessage方法拉取消息,会调用DefaultMQPushConsumerImpl类的pullMessage方法,需要注意封装拉取请求时的几个与broker端长轮询相关的参数
在这里插入图片描述

PullMessageProcessor类的processRequest方法源码较长,主体逻辑是先对拉取请求参数进行各种校验,然后从consumequeue文件和commitlog文件中获取消息,最后对消息获取结果进行处理。我们重点关注拉取不到消息的情况,没有拉取到消息的处理逻辑如下:

在这里插入图片描述

在这里插入图片描述

发现当拉取不到消息后,会重新封装一个PullRequest对象,然后调用PullRequestHoldService的suspendPullRequest方法来处理。

PullRequestHoldService是一个线程类,重点关注其suspendPullRequest方法和run方法。
PullRequestHoldService#suspendPullRequest
发现suspendPullRequest方法只是把PullRequest对象和topic、queueId信息放入容器中,达到hold住的目的,以便后续处理。

run方法
PullRequestHoldService的run方法里面每隔5秒执行一次checkHoldRequest()方法。而checkHoldRequest()方法最终会调用notifyMessageArriving方法。

在这里插入图片描述notifyMessageArriving方法核心逻辑

notifyMessageArriving会取出ManyPullRequest中的所有PullRequest对象,基于pullRequest拉取对象中的offset和需拉取队列的最大offset判断是否有新消息到达,若有新消息到达,则会调用PullMessageProcessor类的processRequest方法来处理消息。
在这里插入图片描述 PullMessageProcessor#executeRequestWhenWakeup()方法

注意,此时将brokerAllowSuspend参数设置为false。

小结一下:当consumer端拉取不到消息的时候,broker端会将消息拉取请求PullRequest hold住,然后每隔5秒检查是否有新消息到达,若有新消息达到就会调用消息处理逻辑进行处理。深入思考一下,如果只采用这种隔5秒检查一下的处理方式是不是会有消息延迟呢?确实会有时效性问题。RocketMQ的解决方案又是什么呢?查看MessageArrivingListener的arriving方法,发现DefaultMessageStore的内部类ReputMessageService的doReput方法也调用了消息到达的监听方法。

在这里插入图片描述
阅读源码发现此处在消息到达broker写入到commitlog文件并且 reput 到 consumeQueue 和 indexFile 后,会触发新消息到达的监听事件,进而到达近实时的效果。

参考文章:
https://mp.weixin.qq.com/s/0OWHwv3N9CcgB2atWGMa1A
《RocketMQ技术内幕》

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值