rocketmq-push模式-消费侧拉取线程-PullMessageService分析

Push模式下,消息拉取分析

上文讲解了重平衡线程、更新topic路由处理rocketmq-push模式-消费侧重平衡-类流程图分析
继续做消息拉取线程PullMessageService分析。
工作流程如下11:
在这里插入图片描述

步骤分析:

步骤1、从阻塞队列pullRequestQueue里获取PullRequest。上篇文章提到,pullRequest的初始来源是RebalanceService重平衡后添加的。

public void run() {
    log.info(this.getServiceName() + " service started");

    while (!this.isStopped()) {
        try {
            PullRequest pullRequest = this.pullRequestQueue.take();
            this.pullMessage(pullRequest);
        } catch (InterruptedException ignored) {
        } catch (Exception e) {
            log.error("Pull Message Service Run Method exception", e);
        }
    }

    log.info(this.getServiceName() + " service end");
}

步骤二、对PullRequest请求体做一些校验。如

  • Dropped校验
    首先PullRequest的请求体,粒度是topic的一个队列。重平衡后,可能该队列已经不再由当前节点负责,就会将状态设置为dropped,代表丢弃处理。
  • 当前内存消息数量大小,内存size大小。
    如果不满足,则会做延时处理

步骤三、构建拉取请求的回调方法对象PullCallback

回调方法包含两个,成功或者异常的处理

package org.apache.rocketmq.client.consumer;

/**
 * Async message pulling interface
 */
public interface PullCallback {
    void onSuccess(final PullResult pullResult);

    void onException(final Throwable e);
}

步骤四、调用pullKernelImpl核心方法,构建PullMessageRequestHeader请求头

详细看org.apache.rocketmq.client.impl.consumer.PullAPIWrapper#pullKernelImpl

步骤五、调用netty发起异步请求,请求broker

详情看
org.apache.rocketmq.client.impl.MQClientAPIImpl#pullMessage
org.apache.rocketmq.client.impl.MQClientAPIImpl#pullMessageAsync
org.apache.rocketmq.remoting.netty.NettyRemotingClient#invokeAsync
主要就是发起异步,构建回调处理
org.apache.rocketmq.remoting.InvokeCallback#operationComplete

private void pullMessageAsync(
    final String addr,
    final RemotingCommand request,
    final long timeoutMillis,
    final PullCallback pullCallback
) throws RemotingException, InterruptedException {
    this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
        @Override
        public void operationComplete(ResponseFuture responseFuture) {
            RemotingCommand response = responseFuture.getResponseCommand();
            if (response != null) {
                try {
                    PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);
                    assert pullResult != null;
                    pullCallback.onSuccess(pullResult);
                } catch (Exception e) {
                    pullCallback.onException(e);
                }
            } else {
                if (!responseFuture.isSendRequestOK()) {
                    pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
                } else if (responseFuture.isTimeout()) {
                    pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
                        responseFuture.getCause()));
                } else {
                    pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
                }
            }
        }
    });
}

步骤六、InvokeCallback处理broker返回的ResponseFuture

首先解析成PullResult,然后再用pullCallback回调成功或者异常

步骤七、pullCallback中根据pullStatus做处理

public enum PullStatus {
    /**
     * Founded
     */
    FOUND,
    /**
     * No new message can be pull
     */
    NO_NEW_MSG,
    /**
     * Filtering results can not match
     */
    NO_MATCHED_MSG,
    /**
     * Illegal offset,may be too big or too small
     */
    OFFSET_ILLEGAL
}

case FOUND,代表拉取到消息。

  • 处理下offetSet,这块还没细看。从消息列表中,可以获取到firstMsgOffset等,详细看org.apache.rocketmq.client.impl.consumer.ProcessQueue#putMessage
  • 并发处理收到的消息列表List《MessageExt》 msgs,详细看
    org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService#submitConsumeRequest
    这里细节比较多,只讲一下主流程。大概讲一下,就是从一个队列里,获取到msg的List,比如10条。
    对于非顺序消息,走ConsumeMessageConcurrentlyService并发消费。
    这10条会分别提交给一个线程池,名称是ConsumeMessageThread_consumerGroup(当前的消费者组的名称)
    这10条会包装成consumeRequest请求体。执行consumeRequest的run()方法。10条并发执行
    run()方法获取到MessageListenerConcurrently的消息回调方法。调用方法前后,可执行executeHookBefore和executeHookAfter。
    执行消息回调方法后,要返回状态。
public enum ConsumeConcurrentlyStatus {
    /**
     * Success consumption
     */
    CONSUME_SUCCESS,
    /**
     * Failure consumption,later try to consume
     */
    RECONSUME_LATER;
}

如果是CONSUME_SUCCESS,则更新ProcessQueue队列的内存数据,包括msgSize等。
更新RemoteBrokerOffsetStore的ConcurrentMap<MessageQueue, AtomicLong> offsetTable
有一个5秒的定时任务,会将该offset反馈给broker。对于集群模式,offset是在broker端保存的。

步骤8、将PullRequest放回阻塞队列pullRequestQueue

第7步讲到,如果一个队列该次拉取有10条消息,那么会提交给线程池(ConsumeMessageThread_consumerGroup)处理。意味着消息的真正回调处理,是异步的。不等待处理完成,会将该拉取请求PullRequest再次投入到PullMessageService现成的阻塞队列pullRequestQueue中,继续请求下一次的拉取。

总结

初始的拉取请求,来源于重平衡线程RebalanceService创建的PullRequest。
这里解析下PullRequest的业务含义。比如有一个topic叫TopicAAA,它在3个broker上分别有4个读队列,意味着这个topic下有12个队列。保存在RebalanceImpl的成员变量ConcurrentMap<String/* topic */, Set MessageQueue>> topicSubscribeInfoTable。
假设消费者组有4个client组成。计算当前client,需要处理的队列是第7、8、9的队列.
那么,就会构建出三个PullRequest请求体,分别对应这3个队列,给到PullMessageService的阻塞队列pullRequestQueue中。
意味着一个PullRequest是对应一个队列的拉取请求。

PullMessageService线程获取到后,就RPC请求broker,拉取队列的消息。比如该队列返回了10条msg。那么会异步的并发消费,调用messageListener的回调方法。
把该PullRequest又重新加入到PullMessageService的阻塞队列pullRequestQueue中。做无限的循环。
至此。已经分析完拉取消息的总体流程。

优缺点分析:push并不是broker端主动发起,依然是consumer端主动拉取。拉取的频率猜测可能会比较高,需要结合broker端的代码做分析。要和DefaultLitePullConsumerImpl做比对。为什么一个叫Push,一个叫Pull。

下一步梳理:

  • 并发消费后,需要更新offset
    集群模式下,需要通知给broker。之前文章已经分析到,是MQClientFactoryScheduledThread 线程,每5秒持久化一次所有的offset给到broker上org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore#persistAll

  • 对DefaultMQPushConsumerImpl和DefaultLitePullConsumerImpl做比对。观察重平衡和拉取消息的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值