RocketMQ 消费者
本文分析 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 消费者的更多相关文章
- RocketMQ消费者示例程序
转载请注明出处:http://www.cnblogs.com/xiaodf/ 本博客实现了一个简单的RocketMQ消费者的示例,MQ里存储的是经过Avro序列化的消息数据,程序读取数据并反序列化后, ...
- 深入研究RocketMQ消费者是如何获取消息的
前言 小伙伴们,国庆都过的开心吗?国庆后的第一个工作日是不是很多小伙伴还沉浸在假期的心情中,没有工作状态呢? 那王子今天和大家聊一聊RocketMQ的消费者是如何获取消息的,通过学习知识来找回状态吧. ...
- RocketMQ消费者实践
最近工作中用到了RocketMQ,现记录下,如何正确实现消费~ 消费者需要注意的问题 防止重复消费 如何快速消费 消费失败如何处理 Consumer具体实现 防止重复消费 重复消费会造成数据不一致等问 ...
- RocketMQ入门案例
学习RocketMQ,先写一个Demo演示一下看看效果. 一.服务端部署 因为只是简单的为了演示效果,服务端仅部署单Master模式 —— 一个Name Server节点,一个Broker节点.主要有 ...
- rocketmq 控制台 trackType NOT_CONSUME_YET
1. 问题描述 rocketmq消费者偶有没有收到消息,查看后台, 显示NOT_CONSUME_YET 2. 分析 mq控制台 显示有该条消息数据 只是状态为未消费 那么问题应该出在 消费者一方 诶? ...
- RocketMQ学习分享
消息队列的流派 什么是 MQ Message Queue(MQ),消息队列中间件.很多人都说:MQ 通过将消息的发送和接收分离来实现应用程序的异步和解偶,这个给人的直觉是——MQ 是异步的,用来解耦的 ...
- SpringBoot2.0 整合 RocketMQ ,实现请求异步处理
一.RocketMQ 1.架构图片 2.角色分类 (1).Broker RocketMQ 的核心,接收 Producer 发过来的消息.处理 Consumer 的消费消息请求.消息的持 久化存储.服务 ...
- RocketMQ之一:RocketMQ整体介绍
常用MQ介绍及对比--<MQ详解及四大MQ比较> RocketMQ环境搭建--<RocketMQ之三:RocketMQ集群环境搭建> RocketMQ物理部署结构 Rocket ...
- RocketMQ问题
RocketMQ原理(4)——消息ACK机制及消费进度管理 RocketMQ消费者,设置setConsumeFromWhere无效的问题 MQ的CONSUME_FROM_LAST_OFFSET未生效 ...
随机推荐
- 上载和下载CSV文件
sap中把txt .excel .文件上载到内表中,txt和csv速度最快. excel文件导出的csv是用,分隔符分隔的,如果单元格的文本中就有逗号,这样会和分隔符逗号混淆,最好abap产生csv文 ...
- div悬浮在屏幕的中间及点击按钮关闭弹出框
#fd { position: fixed; z-index: 999; width: 109px; height: 323px; top: 71%; right: 1%; margin: -50px ...
- Apple历史应用以及开发工具版本(Xcode官方历史版本等等)
1.Xcode 官方历史版本下载:(需要登录开发者账号) https://developer.apple.com/download/more/
- 2019-11-29-Roslyn-如何在-Target-引用-xaml-防止文件没有编译
title author date CreateTime categories Roslyn 如何在 Target 引用 xaml 防止文件没有编译 lindexi 2019-11-29 08:58: ...
- Linux crontab计划任务
1.cron计划任务的描述 cron计划任务允许用户根据“时间表”自动周期的完成任务某些任务. cron是一种system V服务,需要开启该服务才能使用. ...
- 怎样减少 Android 应用包 60% 的大小?
简评: 应用的大小也是用户体验的一个重要方面,而减少 Android 应用安装包大小其实一点也不复杂. 对于移动应用来说,应用安装包的大小当然是越小越好.特别是对于一些欠发达地区,你不希望用户因为手机 ...
- vue打包后element-ui部分样式(图标)异常问题
vue项目使用element-ui组件,打包后部分样式(上下左右箭头)异常,变成方框了. 页面报warn错误,有个字体找不到. 解决办法:在build文件夹下找到utils.js,加上一行public ...
- Caffe学习使用__运行caffe自带的两个简单例子
为了程序的简洁,在caffe中是不带练习数据的,因此需要自己去下载.但在caffe根目录下的data文件夹里,作者已经为我们编写好了下载数据的脚本文件,我们只需要联网,运行这些脚本文件就行了. 注意: ...
- [ZJOI2014]力 题解
题目地址 洛谷P3338 Solution 第一道FFT的应用AC祭! 我们要求: \[E_j=\frac{F_j}{q_j}=\sum_{i<j}\frac{q_i}{(i-j)^2}-\su ...
- Yii和ThinkPHP对比心得
本人小菜鸟一只,为了自我学习和交流PHP(jquery,linux,lamp,shell,javascript,服务器)等一系列的知识,小菜鸟创建了一个群.希望光临本博客的人可以进来交流.寻求共同发展 ...