本文分析 DefaultMQPushConsumer,异步发送消息,多线程消费的情形。

DefaultMQPushConsumerImpl
  MQClientInstance 一个客户端进程只有一个 MQClientInstance 实例
    MQClientAPIImpl 和 netty 打交道
    PullMessageService 拉取消息
    RebalanceService 客户端 rebalance
    ConcurrentMap<String/* group */, MQProducerInner>
    ConcurrentMap<String/* group */, MQConsumerInner>
  ConsumeMessageService 消费消息
    ThreadPoolExecutor 消费线程池
  RebalancePushImpl  负责 rebalance
    ConcurrentMap<MessageQueue, ProcessQueue>

消费者启动从 DefaultMQPushConsumerImpl#start 开始,创建 MQClientInstance 实例,创建 ConsumeMessageService 实例,然后分别启动 ConsumeMessageService#start, MQClientInstance#start,这时 PullMessageService 和 RebalanceService 就开始工作了。目前来看,常规用法中,在一个 jvm 进程中,只有一个 MQClientInstance 对象,对象中存储了 jvm 中所有的 producer 和 consumer。

org.apache.rocketmq.client.impl.consumer.RebalanceService#run

public void run() {
log.info(this.getServiceName() + " service started"); while (!this.isStopped()) {
this.waitForRunning(waitInterval);
this.mqClientFactory.doRebalance();
} log.info(this.getServiceName() + " service end");
}

RebalanceService 一会睡眠,一会工作,当消费者数量发生变化时,broker 会推送通知给所有消费者,消费者唤醒 rebalance 线程,开始工作。

所有的客户端按照相同的算法,分配 queue,然后把结果保存到 processQueueTable 中。

在第一次 rebalance 时,会创建 PullRequest 放入 PullMessageService 的队列中,开启了拉取消息的序幕。

// mqSet 是分配给当前消费者的 queue
private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
final boolean isOrder) {
boolean changed = false; Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
while (it.hasNext()) {
Entry<MessageQueue, ProcessQueue> next = it.next();
MessageQueue mq = next.getKey();
ProcessQueue pq = next.getValue(); if (mq.getTopic().equals(topic)) {
if (!mqSet.contains(mq)) {
// 该 queue 分配给其他消费者了
pq.setDropped(true);
if (this.removeUnnecessaryMessageQueue(mq, pq)) {
it.remove();
changed = true;
log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
}
} else if (pq.isPullExpired()) {
switch (this.consumeType()) {
case CONSUME_ACTIVELY:
break;
case CONSUME_PASSIVELY:
pq.setDropped(true);
if (this.removeUnnecessaryMessageQueue(mq, pq)) {
it.remove();
changed = true;
log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it",
consumerGroup, mq);
}
break;
default:
break;
}
}
}
} // 为新分配的 queue 创建 PullRequest 请求
List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
for (MessageQueue mq : mqSet) {
if (!this.processQueueTable.containsKey(mq)) {
if (isOrder && !this.lock(mq)) {
log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
continue;
} this.removeDirtyOffset(mq);
ProcessQueue pq = new ProcessQueue();
long nextOffset = this.computePullFromWhere(mq);
if (nextOffset >= 0) {
ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
if (pre != null) {
log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
} else {
log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
PullRequest pullRequest = new PullRequest();
pullRequest.setConsumerGroup(consumerGroup);
pullRequest.setNextOffset(nextOffset);
pullRequest.setMessageQueue(mq);
pullRequest.setProcessQueue(pq);
pullRequestList.add(pullRequest);
changed = true;
}
} else {
log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
}
}
} this.dispatchPullRequest(pullRequestList); return changed;
}

rocketmq 拉取消息是客户端源源不断拉取的,在收到 PullRequest 的响应后,会继续创建一个 PullRequest 放入队列,无限循环下去。

org.apache.rocketmq.client.impl.consumer.PullMessageService#run

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");
}

注册回调函数
org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#pullMessage

拉取消息有流控逻辑,对于每一个分区,如果 cachedMessageCount 大于 1000,cachedMessageSizeInMiB 大于 100M,则延迟执行 PullRequest。把 PullRequest 重新放入队列。

PullCallback pullCallback = new PullCallback() {
@Override
public void onSuccess(PullResult pullResult) {
if (pullResult != null) {
pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
subscriptionData); switch (pullResult.getPullStatus()) {
case FOUND:
long prevRequestOffset = pullRequest.getNextOffset();
pullRequest.setNextOffset(pullResult.getNextBeginOffset());
long pullRT = System.currentTimeMillis() - beginTimestamp;
DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(pullRequest.getConsumerGroup(),
pullRequest.getMessageQueue().getTopic(), pullRT); long firstMsgOffset = Long.MAX_VALUE;
if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) {
DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
} else {
firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset(); DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(),
pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size());
// 拉取到的消息放入 processQueue
boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
// 创建 ConsumeRequest 放入 consumeMessageService 的线程池中
DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(
pullResult.getMsgFoundList(),
processQueue,
pullRequest.getMessageQueue(),
dispatchToConsume); if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) {
DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest,
DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
} else {
DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
}
} if (pullResult.getNextBeginOffset() < prevRequestOffset
|| firstMsgOffset < prevRequestOffset) {
log.warn(
"[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}",
pullResult.getNextBeginOffset(),
firstMsgOffset,
prevRequestOffset);
} break;
case NO_NEW_MSG:
pullRequest.setNextOffset(pullResult.getNextBeginOffset()); DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest); DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
break;
case NO_MATCHED_MSG:
pullRequest.setNextOffset(pullResult.getNextBeginOffset()); DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest); DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
break;
case OFFSET_ILLEGAL:
log.warn("the pull request offset illegal, {} {}",
pullRequest.toString(), pullResult.toString());
pullRequest.setNextOffset(pullResult.getNextBeginOffset()); pullRequest.getProcessQueue().setDropped(true);
DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() { @Override
public void run() {
try {
DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(),
pullRequest.getNextOffset(), false); DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue()); DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue()); log.warn("fix the pull request offset, {}", pullRequest);
} catch (Throwable e) {
log.error("executeTaskLater Exception", e);
}
}
}, 10000);
break;
default:
break;
}
}
} @Override
public void onException(Throwable e) {
if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
log.warn("execute the pull request exception", e);
} DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
}
};

回调传递给通信层
org.apache.rocketmq.client.impl.MQClientAPIImpl#pullMessageAsync

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);
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()));
}
}
}
});
}

触发回调
NettyRemotingClient.NettyClientHandler#channelRead0
org.apache.rocketmq.remoting.netty.ResponseFuture#executeInvokeCallback

消息消费
org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.ConsumeRequest#run

RocketMQ 消费者的更多相关文章

  1. RocketMQ消费者示例程序

    转载请注明出处:http://www.cnblogs.com/xiaodf/ 本博客实现了一个简单的RocketMQ消费者的示例,MQ里存储的是经过Avro序列化的消息数据,程序读取数据并反序列化后, ...

  2. 深入研究RocketMQ消费者是如何获取消息的

    前言 小伙伴们,国庆都过的开心吗?国庆后的第一个工作日是不是很多小伙伴还沉浸在假期的心情中,没有工作状态呢? 那王子今天和大家聊一聊RocketMQ的消费者是如何获取消息的,通过学习知识来找回状态吧. ...

  3. RocketMQ消费者实践

    最近工作中用到了RocketMQ,现记录下,如何正确实现消费~ 消费者需要注意的问题 防止重复消费 如何快速消费 消费失败如何处理 Consumer具体实现 防止重复消费 重复消费会造成数据不一致等问 ...

  4. RocketMQ入门案例

    学习RocketMQ,先写一个Demo演示一下看看效果. 一.服务端部署 因为只是简单的为了演示效果,服务端仅部署单Master模式 —— 一个Name Server节点,一个Broker节点.主要有 ...

  5. rocketmq 控制台 trackType NOT_CONSUME_YET

    1. 问题描述 rocketmq消费者偶有没有收到消息,查看后台, 显示NOT_CONSUME_YET 2. 分析 mq控制台 显示有该条消息数据 只是状态为未消费 那么问题应该出在 消费者一方 诶? ...

  6. RocketMQ学习分享

    消息队列的流派 什么是 MQ Message Queue(MQ),消息队列中间件.很多人都说:MQ 通过将消息的发送和接收分离来实现应用程序的异步和解偶,这个给人的直觉是——MQ 是异步的,用来解耦的 ...

  7. SpringBoot2.0 整合 RocketMQ ,实现请求异步处理

    一.RocketMQ 1.架构图片 2.角色分类 (1).Broker RocketMQ 的核心,接收 Producer 发过来的消息.处理 Consumer 的消费消息请求.消息的持 久化存储.服务 ...

  8. RocketMQ之一:RocketMQ整体介绍

    常用MQ介绍及对比--<MQ详解及四大MQ比较> RocketMQ环境搭建--<RocketMQ之三:RocketMQ集群环境搭建> RocketMQ物理部署结构 Rocket ...

  9. RocketMQ问题

    RocketMQ原理(4)——消息ACK机制及消费进度管理 RocketMQ消费者,设置setConsumeFromWhere无效的问题 MQ的CONSUME_FROM_LAST_OFFSET未生效 ...

随机推荐

  1. Github删除仓库文件夹问题集合

    记得上次使用GitHub,看时间提示,最近的一次,是三年前,而且都是长传文件,这次是删除文件,才发现删除库可以,但是删除库里的某个目录,就不行了,除非是下载下来,在GitHub把仓库删了重新添加.使用 ...

  2. 【BZOJ1492】【Luogu P4027】 [NOI2007]货币兑换 CDQ分治,平衡树,动态凸包

    斜率在转移顺序下不满足单调性的斜率优化\(DP\),用动态凸包来维护.送命题. 简化版题意:每次在凸包上插入一个点,以及求一条斜率为\(K\)的直线与当前凸包的交点.思路简单实现困难. \(P.s\) ...

  3. 美团点评SQL优化工具SQLAdvisor开源快捷部署

    美团点评SQL优化工具SQLAdvisor开源快捷部署 git clone https://github.com/Meituan-Dianping/SQLAdvisor.gityum install ...

  4. R语言-六大数据结构

    R语言有六种基本的数据结构(或者说数据类型吧).根据数据的维度和同质/异质可分为5种数据类型,最后再介绍一种特殊的类型“因子”.   同质 异质 1维 原子向量 列表 2维 矩阵 数据框 n维 数组 ...

  5. sqlserver表值函数调用方式

    Connection conn = sqlServerManage.sqlServerConn(); Statement stmt; ResultSet rs; // 组装sql StringBuff ...

  6. 【NOIP2017提高组模拟12.17】环

    题目 小A有一个环,环上有n个正整数.他有特殊的能力,能将环切成k段,每段包含一个或者多个数字.对于一个切分方案,小A将以如下方式计算优美程度: 首先对于每一段,求出他们的数字和.然后对于每段的和,求 ...

  7. 【NOIP2017模拟12.3】子串

    题目 分析 对于当前枚举串 \(now\),从前往后扫.若扫到 \(i\),\(s_i\) 是 ; \(s_j\) 的子串 \((i < j < now)\),我们就可以跳过不匹配 \(i ...

  8. hdu 3376 : Matrix Again【MCMF】

    题目链接 题意:给定一个n*n的矩阵,找一条路,从左上角到右下角再到左上角,每个点最多经过一次,求路径上的点的权值的最大和. 将矩阵中每个点拆点,点容量为1,费用为点权值的相反数.每个点向自己右侧和下 ...

  9. springCloud——Eureka、Ribbon理解

    一. 服务注册中心.服务提供者.服务消费者 如何通信? 客户端: 应用主类中配置@EnableDiscoveryClient application.properties中配置defaultZone指 ...

  10. electron打包成.exe后限制只启动一个应用

    注意:这是2.x的文档 const {app} = require('electron') let myWindow = null const shouldQuit = app.makeSingleI ...