202412/30回过头来记录一下:之前一直找不到在哪里把消息丢给消费者(2024/11/23),找了很久都没找到,就放弃了,然后发现,我靠原来是在poplongpollingservice里。。。。还是乱打断点,偶然找到的。。。这属于运气好,好的,接下来处理consumer流程
RocketMQ主要有两种消费模式:CLUSTERING集群消费(默认)和BROADCASTING广播消费。在CLUSTERING模式下,一个ConsumerGroup中的Consumer实例根据队列分配策略算法为Consumer分配队列,平均分摊(默认)消费消息。例如,如果Topic是Test的消息发送到该主题的不同队列中,发送了有100条消息,其中一个ConsumerGroup有3个Consumer实例,那么根据队列分配算法,每个队列都会有消费者,每个消费者实例只消费自己队列上的数据,消费完的消息不能被其他消费实例消费。在BROADCASTING模式下,消息会被所有在该Topic的ConsumerGroup中的Consumer实例共同消费。消费模式在创建Consumer时指定,不同的消费模式其内部机制也不同,消息的消费方式、记录消费进度、消息的消费状态等也都各不相同。
对于长时间没有 ACK 的消息,Broker 端并非毫无办法。Pop 消费引入了消息不可见时间(invisibleTime)的机制。当 Pop 出一条消息后,这条消息对所有消费者不可见,即进入不可见时间,当它超过该时刻还没有被 ACK,Broker 将会把它放入 Pop 专门的重试 Topic(这个过程称为 Revive),这条消息重新可以被消费。https://my.oschina.net/u/4249738/blog/5609999
rocketmq5有三种消费者:默认是pop
CONSUME_ACTIVELY("PULL") --
CONSUME_PASSIVELY("PUSH") --
CONSUME_POP("POP") -- 对应PopLongPollingService
顺序写随机读:
发消息时,Productor的消息确实是顺序写CommitLog
订阅消息时,Consumer也是顺序读ConsumeQueue,然而根据其中的起始物理位置偏移量offset读取消息真实内容却是随机读CommitLog。
https://zhouj000.github.io/2021/05/10/rocketmq-source1/
pullRequestHoldService
ReputMessageService
源码阅读建议:因为rocketmq中有很多topic,而同一套代码流程可能同时处理我们的生产者消费者,也可能处理系统的相关消息,比如heartbeat,所以为了打断点的时候能确保是我们的代码触发的,所以建议如下修改源码,这样一旦运行到断点处就能确定是我们的代码触发的:
@Override
public GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset,
final int maxMsgNums, final int maxTotalMsgSize, final MessageFilter messageFilter) {
if (this.shutdown) {
LOGGER.warn("message store has shutdown, so getMessage is forbidden");
return null;
}
......
if (topic.equals("testz")){
#testz是我们自己的代码中使用的topic
System.out.println("i found you:testz"); #随便来两条语句,然后在这里打断点
int a=1; #这样就能确保运行到这个断点的时候topic是我们自己的topic
#而不会被heartbeat等系统消息所干扰
}
......
消费逻辑:
client sdk中consumer有两种消息拉取模式:broker push和consumer pull
20241230 补充:rocketmq5还有pop方式
三个where:
commitWhere表示消费者已经消费到哪了,也是另开一个线程去flush即在CommitRealTimeService.run中刷到磁盘
flushWehre表示数据已经刷盘到哪里了,flushWhere的数据也叫confirmedOffset
妈的,一大堆offset、where,都绕晕了
ReputService:
ReputService是DefaultMessageStore的一个内部类
ReputService.run
while !this.isStopped:
ReputService.doReput
if ReputService.reputFromOffset < CommitLog.getMinOffset: #commitLog的minOffset表示目前存在的消息的最小offset
#因为offset是递增的,所以这就表示凡是offset小于minOffset
#的消息都已经被删除了,也就是对应的文件expired了
ReputService.reputFromOffset = CommitLog.getMinOffset #所以此时需要重置reputOffset为minOffset
#即第一条有效地commitLog
while doNext:
ok=isCommitLogAvailable: #检测commitLog的offset是否有变动,
new=ReputService.getReputEndOffset #首先获取commitLog的位置
#有两个offset:maxOffset和confirmedOffset
#maxOffset表示commitLog的当前位置
#并且这一块数据可能还没有flush到磁盘,所以是不稳定的
#confirmedOffset则是已经刷新到磁盘的数据的最大offset
#所以如果开启读未提交则返回maxOffset,反之返回confirmedOffset
MessageStoreConfig.isReadUnCommitted?CommitLog.getMaxOffset():CommitLog.getConfirmOffset()
return cur<new #一旦检测到commitLog的offset大于记录的reputOffset
#就说明有更新即本次需要doReput而不是sleep
CommitLog.getData(reputFromOffset) #从commitLog获取新增的数据
#所有数据都是存放在commitLog,consumerQueue存的是索引没有存原始数据
CommitLog.checkMessageAndReturnSize #检查消息是否ok比如检查crc对不对,以及获取消息大小
if reputFromOffset + size > getReputEndOffset(): #如果当前reputOffset+msgSize超过了offset
doNext = false #就表示这不是一条完整的消息即所有消息都处理完了,可以结束本次循环
break
if dispatchRequest.isSuccess(): #如果这条消息ok
if size > 0: #如果size大于0
ReputService.doDispatch #分发消息,默认有4个dispatch,分别是:
#CommitLogDispatcherCalcBitMap:
#DefaultMessageStore.CommitLogDispatcherBuildConsumeQueue、
#DefaultMessageStore.CommitLogDispatcherBuildIndex、
#CommitLogDispatcherCompaction
1: 计算consumerQueue对应的bitmapFilter
CommitLogDispatcherCalcBitMap.dispatch #1:CommitLogDispatcherCalcBitMap计算consumerQueue对应的bitmapFilter
...略... #估计是用来加快查找的,和indexFile类似
#不过默认是不启用的
2:把offset写入consumerQueue文件
CommitLogDispatcherBuildConsumeQueue.dispatch #2:把offset写入consumerQueue文件
switch (tranType):
case MessageSysFlag.TRANSACTION_NOT_TYPE: #如果是非事务消息即普通消息
case MessageSysFlag.TRANSACTION_COMMIT_TYPE: #或者是COMMIT消息即已完成的事务消息
#即prepare或者rollback的消息我们就不需要写入consumerQueue
DefaultMessageStore.putMessagePositionInfo #执行分发
ConsumerQueueStore.putMessagePositionInfoWrapper #ConsumerQueueStoreInterface可以有多种实现:default和rocksdb
ConsuemrQueueStore.findOrCreateConsumeQueue #分发时首先查找consuemrQueue对应的文件
#会把msg的offset写入consumerQueue对应的文件
#consumerQueue是构建在commitLog之上的逻辑队列
#consumerQueue中只保存msg的offset
#读取一个消息的时候要先从consuemrQueue对应的文件中