最近在给社区提PR和补充文档的时候跟踪了不少源码,调整了下状态,记录在此一些心得。本人阅读的是rocketmq5.0的源码,目前Rocketmq的Consumer支持三种模式Pop,Push,Pull模式,Producer支持两种模式Push和Pull
ps: 不过最大的动力是为了近期RIP-46 生产者幂等发送的编写做准备
- POP Consumer 作为 5.0 的一大特性,POP 消费模式展现了一种全新的消费模式。其具备的轻量级,无状态,无队列独占等特点,对于消息积压场景,Streaming 消费场景等都非常友好
- Pull Consumer 需要手动调用consumer拉消息
- Push Consumer 只需要提供一个listener即可实现对消息的监听
下面将对Pull模式下的Produder和Consumer进行分析
在源码中找到NamesrvStartup和BrokerStartup 启动main函数 让我们写一个demo
消费者
用默认生产者生成数据
org.apache.rocketmq.example.simple.TestProducer
import org.apache.rocketmq.client.QueryResult;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class TestProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
producer.start();
for (int i = 0; i < 1; i++)
try {
{
Message msg = new Message("TopicTest1",
"TagA",
"key113",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
QueryResult queryMessage =
producer.queryMessage("TopicTest1", "key113", 10, 0, System.currentTimeMillis());
for (MessageExt m : queryMessage.getMessageList()) {
System.out.printf("%s%n", m);
}
}
} catch (Exception e) {
e.printStackTrace();
}
producer.shutdown();
}
}
类路径
org.apache.rocketmq.example.simple.PullConsumer
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageQueue;
public class PullConsumer {
// 记录每个队列的消费进度
private static final Map<MessageQueue, Long> OFFSE_TABLE = new HashMap<MessageQueue, Long>();
public static void main(String[] args) throws MQClientException {
// 1. 创建DefaultMQPullConsumer实例
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("test_consumer");
// 2. 设置NameServer
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.start();
// 3. 获取Topic的所有队列
Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("TopicTest");
// 4. 遍历所有队列
for (MessageQueue mq : mqs) {
System.out.printf("Consume from the queue: %s%n", mq);
SINGLE_MQ:
while (true) {
try {
// 5. 拉取消息 输入参数依次为(消息队列,tag消息过滤,消息队列,一次最大拉去消息数量)
PullResult pullResult =
consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32);
System.out.printf("%s%n", pullResult);
// 6. 将消息放入hash表中,存储该队列的消费进度
putMessageQueueOffset(mq, pullResult.getNextBeginOffset());
switch (pullResult.getPullStatus()) {
case FOUND: // 找到消息,输出
System.out.println(pullResult.getMsgFoundList().get(0));
break;
case NO_MATCHED_MSG: // 没有匹配tag的消息
System.out.println("无匹配消息");
break;
case NO_NEW_MSG: // 该队列没有新消息,消费offset=最大offset
System.out.println("没有新消息");
break SINGLE_MQ; // 跳出该队列遍历
case OFFSET_ILLEGAL: // offset不合法
System.out.println("Offset不合法");
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 7. 关闭Consumer
consumer.shutdown();
}
/**
* 从Hash表中获取当前队列的消费offset
* @param mq 消息队列
* @return long类型 offset
*/
private static long getMessageQueueOffset(MessageQueue mq) {
Long offset = OFFSE_TABLE.get(mq);
if (offset != null)
return offset;
return 0;
}
/**
* 将消费进度更新到Hash表
* @param mq 消息队列
* @param offset offset
*/
private static void putMessageQueueOffset(MessageQueue mq, long offset) {
OFFSE_TABLE.put(mq, offset);
}
}
debug参考前一篇的配置方式
- 实现方式
名词解释: MessageQueue: RocketMQ的所有主题都是由多个队列组成,以此实现队列数量的水平拆分和队列内部的流式存储。
生产者: 发送消息的进程
消费者:消费消息的进程
订阅关系: 订阅关系一致指的是同一个消费者Group ID下所有Consumer实例所订阅的Topic、Tag必须完全一致。如果订阅关系不一致,消息消费的逻辑就会混乱,甚至导致消息丢失。
-
获取消息队列
public Set<MessageQueue> fetchSubscribeMessageQueues(String topic) -
同步拉取消息
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout) -
同步阻塞拉取消息
public PullResult pullBlockIfNotFound(MessageQueue mq, String subExpression, long offset, int maxNums) -
获取队列的消费Offset
public long fetchConsumeOffset(MessageQueue mq, boolean fromStore) -
更新消费组Offset
public void updateConsumeOffset(MessageQueue mq, long offset)
时序图: www.processon.com/view/link/6…
看起来 fetchConsumeOffset和updateConsumeOffset的细节还没有完全展开 可能需要在netty的ProcessRemotingRequest逻辑当中 对代码做进一步展开
Continue...QwQ