经典的推模式/拉模式
首先,明确一下业务场景
这里谈论的推拉模式,指的是 Consumer 和 Broker 之间,不是 producer与broker之间。
经典的推模式
经典的推模式,指的是消息从 Broker 推向 Consumer。
Consumer 被动的接收消息,由 Broker 来主导消息的发送。作为代理人,Broker 接受完消息之后,可以立马推送给 Consumer。
Consumer等着就行,消息会有broker主动推过来。所以 Consumer 的处理策略很简单。
推模式的缺点:Consumer可能就“消化不良/OOM”。
当 Broker 推送消息的速率大于Consumer消费速率时,Consumer可能就“消化不良”,出现内存积压,内存溢出,OOM,因为根本消费不过来啊。
所以,经典的推模式,适用于消息量不大、Consumer消费能力强的场景。
经典的拉模式
经典的拉模式,指的是 Consumer 主动向 Broker 请求拉取消息。
上面讲到,经典的推模式,适用于消息量不大、Consumer消费能力强的场景。如果Consumer消费能力弱, 那么就改变方向好了, 由推改完拉。
拉的话,主动权就在Consumer身上了, 能消化多少,吃多少。
假设当前Consumer 消化不过来、消费不过来了,它可以根据一定的策略,暂停拉取甚至停止拉取,或者间隔拉取都行。
凡事有利必有弊。
拉模式的缺点:消息延迟+消息积压。 如果Consumer 隔个 2天采取拉取一批,消息就很有可能延迟,甚至出现严重的消息延迟。而且Broker 服务端大概率会消息积压。
推拉模式,如何选型?
选择推模式的消息队列中间件,主要有ActiveMQ
选择推模式的消息队列中间件,主要有RocketMQ 和 Kafka
虽然RocketMQ 和 Kafka都选择了拉模式。也就是允许消息延迟 + 允许消息积压。
所以,选择RocketMQ 和 Kafka,就需要做好消息积压的监控。
Rocketmq 的推模式和拉模式
Rocketmq 的客户端,也定义了两个模式:推模式和拉模式
但是实际上,RocketMQ 中的 PushConsumer 推模式,仅仅是披着拉模式的方法,本质还是拉模式。
RocketMQ 中的 PushConsumer 推模式
接下来,我们首先看看RocketMQ 中的 PushConsumer 推模式。
一个消费者至少需要涉及队列自动负载、消息拉取、消息消费、位点提交、消费重试等几个部分。
MQClientInstance 客户端实例,会开启多个异步并行服务:
-
负载均衡服务 rebalanceService :再平衡服务.
专门进行 queue分区的 再平衡,再分配,然后发布拉取消息的请求 pullRequest 实例。
-
消息拉取服务 pullMessageService:专门负责拉取消息。
从请求队列 pullRequestQueue 队列 获取一个一个的 pullRequest,
通过内部实现类DefaultMQPushConsumerImpl 拉取 消息。
注意,拉取的消息,放在另一个队列 messageQueue 缓存,拉取之前,会进行流控检查,如果这个队列满了(>1000个消息或者 >100M内存) 则延迟50ms再拉取, 当然,下一次执行拉取之前,同样也会进行流控检查
-
消息消费线程:ConsumeMessageOrderlyService 有序消息消费, 或者 并行消息。 从messageQueue 拉取消息,进行消费。
上面设计3类线程,在3类线程之间,通过两个队列进行 同步:
-
拉取消息的请求队列 pullRequestQueue
-
缓存消息的队列 messageQueue
Rocketmq 的推模式,本质是一种拉模式, 只是为了让客户端不会 累死, 在拉取之前进行流控。
// 接下来就是消费者的拉取流量控制,阈值为 1000个消息, 或者 100M
// 消费者消费的太慢了,broker推送的太快了,进行 Flow control
if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) {
// 将pullRequest放入队列,只不过是经过后台的定时线程池延50 ms 迟放入,进行 Flow control
// 流量控制, 减缓拉取消息的速度
// * Flow control threshold on queue level, each message queue will cache at most 1000 messages by default,
// * Consider the {@code pullBatchSize}, the instantaneous value may exceed the limit
// */
// private int pullThresholdForQueue = 1000;
this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
if ((queueFlowControlTimes++ % 1000) == 0) {
log.warn(
&nbs