RocketMQ消息消费-消息拉取(一)

一、RocketMq消费方式

mq的消费代码位置:
在这里插入图片描述
在这里插入图片描述
由代码截图可以看出消费者消费有两种消费,一种是推,一种是拉。

1)Push方式:由消息中间件(MQ消息服务器代理)主动地将消息推送给消费者;采用Push方式,可以尽可能实时地将消息发送给消费者进行消费。但是,在消费者的处理消息的能力较弱的时候(比如,消费者端的业务系统处理一条消息的流程比较复杂,其中的调用链路比较多导致消费时间比较久。概括起来地说就是“慢消费问题”),而MQ不断地向消费者Push消息,消费者端的缓冲区可能会溢出,导致异常;
(2)Pull方式:由消费者客户端主动向消息中间件(MQ消息服务器代理)拉取消息;采用Pull方式,如何设置Pull消息的频率需要重点去考虑,举个例子来说,可能1分钟内连续来了1000条消息,然后2小时内没有新消息产生(概括起来说就是“消息延迟与忙等待”)。如果每次Pull的时间间隔比较久,会增加消息的延迟,即消息到达消费者的时间加长,MQ中消息的堆积量变大;若每次Pull的时间间隔较短,但是在一段时间内MQ中并没有任何消息可以消费,那么会产生很多无效的Pull请求的RPC开销,影响MQ整体的网络性能;

mq的消费我们消费方式的测试类入手:
代码地址:org.apache.rocketmq.client.consumer.DefaultMQPullConsumerTest

  • consumer的初始化,init()
  • consumer的终止,terminate()
  • consume的拉取消息;testPullMessage_Success
  • consume的拉取消息未发现,testPullMessage_NotFound
  • consume的异步拉取消息;testPullMessageAsync_Success

MQ的消息消费方式是拉取消息的方式,消息消费有两种消费方式:广播模式与集群模式,
广播模式;每一个消费方式去拉取订阅主题下所有消息队列的消息,集群模式;在集群模式下,同一个消费组内,有多个消费者,同一个主题存在多个消息队列,消息队列负载,通常的做法是同一个时间内只允许被一个消费者消费,一个消费者可以同时消费多个消息对列。

从MQclient的启动流程来看,RocketMq使用一个单独的线程PullMessageService来负责代码的拉取;

在这里插入图片描述

二、PullMessageService

初始化一个newSingleThreadScheduledExecutor线程池

private final ScheduledExecutorService scheduledExecutorService = Executors
        .newSingleThreadScheduledExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "PullMessageServiceScheduledThread");
            }
        });

定时先线程池执行任务:
在这里插入图片描述
在这里插入图片描述
调用DefaultMQPushConsumerImpl impl 的pullMessage;

schedule中的pullRequestQueue如何实现放入PullRequest的,PullMessageService有两种实现方式;
1、延时放入executePullRequestLater 2、立即放入 executePullRequestImmediately
在这里插入图片描述

2.1、PullRequset

在这里插入图片描述
PullRequset的核心属性:
String consumerGroup :消费者组
MessageQueue :待拉取的消费队列
ProcessQueue processQueue : 消息处理队列
long nextOffset :拉取偏移量
boolean lockedFirst :是否被锁定;

2.2、ProcessQueue实现机制

在这里插入图片描述

//  锁最大存活时间。可以通过-Drocketmq.broker.rebalance.lockMaxLiveTime设置,默认为60s。
public final static long REBALANCE_LOCK_MAX_LIVE_TIME =
        Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockMaxLiveTime", "30000"));
    public final static long REBALANCE_LOCK_INTERVAL = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockInterval", "20000"));
    // 客户端拉取消息最大空闲时间
    private final static long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000"));
    private final InternalLogger log = ClientLogger.getLog();
    // 消息存储map的读写锁
    private final ReadWriteLock lockTreeMap = new ReentrantReadWriteLock();
    // 消息队列的索引下标以及消息体
    private final TreeMap<Long, MessageExt> msgTreeMap = new TreeMap<Long, MessageExt>();
    private final AtomicLong msgCount = new AtomicLong();
    private final AtomicLong msgSize = new AtomicLong();
    private final Lock lockConsume = new ReentrantLock();
    /**
     * A subset of msgTreeMap, will only be used when orderly consume
     */
    private final TreeMap<Long, MessageExt> consumingMsgOrderlyTreeMap = new TreeMap<Long, MessageExt>();
    private final AtomicLong tryUnlockTimes = new AtomicLong(0);
    // 消息队列的最大偏移量
    private volatile long queueOffsetMax = 0L;// 当前ProcessQueue包含的最大偏移量
    private volatile boolean dropped = false;
    // 上一次拉取消息的时间搓
    private volatile long lastPullTimestamp = System.currentTimeMillis();
    // 上一次消费的时间搓
    private volatile long lastConsumeTimestamp = System.currentTimeMillis();
    private volatile boolean locked = false;
    private volatile long lastLockTimestamp = System.currentTimeMillis();
    private volatile boolean consuming = false;
    private volatile long msgAccCnt = 0;
2.2、pullMessage的基本流程

消息拉取主要三个步骤

  1. 消息拉取客户端请求封装
  2. 消息服务器查找并返回消息
  3. 消息拉取客户端处理返回消息

1、客户端封装消息拉取请求

(1)拉取消息的前置判断
在这里插入图片描述
(2)拉取消息的限流判断
在这里插入图片描述
(3)、拉取主题订阅消息

final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
if (null == subscriptionData) {
     this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
     log.warn("find the consumer's subscription failed, {}", pullRequest);
     return;
 }

(4)、调用pullAPIWrapper.pullKernelImpl与系统交互

this.pullAPIWrapper.pullKernelImpl(
                pullRequest.getMessageQueue(),
                subExpression,
                subscriptionData.getExpressionType(),
                subscriptionData.getSubVersion(),
                pullRequest.getNextOffset(),
                this.defaultMQPushConsumer.getPullBatchSize(),
                sysFlag,
                commitOffsetValue,
                BROKER_SUSPEND_MAX_TIME_MILLIS,
                CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
                CommunicationMode.ASYNC,
                pullCallback
            );

MessageQueue mq:从那个消息队列拉取消息

String subExpression:消息过滤表达式

String expressionType:消息表达式类型

long subVersion:

long offset:消息拉取偏移量

int maxNums:本次拉取消息的最大条数

int sysFlag:拉取系统标记

long commitOffset:当前messageQueue的消费进度

long brokerSuspendMaxTimeMillis:消息拉取过程中允许broker挂起的时间,默认15s。

long timeoutMillis:消息拉取的超时时间

CommunicationMode communicationMode:消息拉取的模式,异步拉取

PullCallback pullCallback:从broker拉取消息后的回调方法

(6)根据brokerName,brokerId从MQClientInstance中获取broker的地址

FindBrokerResult findBrokerResult =
            this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                this.recalculatePullFromWhichNode(mq), false);
if (null == findBrokerResult) {
    this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
    findBrokerResult =
        this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
            this.recalculatePullFromWhichNode(mq), false);
}
// 请求拉取消息
  PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
                brokerAddr,
                requestHeader,
                timeoutMillis,
                communicationMode,
                pullCallback);

2、消息服务端Broker组装消息

final GetMessageResult getMessageResult =
            this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
                requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);

String group:消费组名称
String topic:主题名称
int queueId:队列id
long offset:待拉取队列便宜量
int maxMsgNums:最大拉取条数

3、消息拉取客户端处理消息

消息拉取拉取客户端的入口MQClientAPIImpl#pullMessageAsync;
在这里插入图片描述
RocketMq整体拉取流程:
在这里插入图片描述

文章参考:RocketMq架构设计与实现原理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值