11. rocketmq源代码学习---服务端消息拉取&TAG订阅

通过前面7.rocketmq源代码学习----服务端数据接收的学习,我们知道当客户端发送消息时,服务端的处理器是:PullMessageProcessor,接下来我们就来分析下PullMessageProcessor的代码。

从前面文章的分析,我们知道拉取文件肯定是需要从consumequeue中获取消息id,先来简要看下PullMessageProcessor的简单时序图:

在这里插入图片描述

有几个关键的问题:
1、是怎么根据offset找到文件的?
2、是怎么根据offset找到对应位置的?
3、tag过滤存在hash冲突的,hash一致不代表tag一致,rocktemq是怎么处理的?

先来看第1个第2个问题:

1、是怎么根据offset找到文件的?
2、是怎么根据offset找到对应位置的?

根据前面消息存储结构的分析:https://blog.csdn.net/u013286936/article/details/85308290

我们知道consumequeue文件里面,一个存储单元固定是20byte,那么一个consumequeue文件存储多少个单元呢?
在MessageStoreConfig中可以找到答案:

// ConsumeQueue每个文件大小 默认存储30W条消息
    private int mapedFileSizeConsumeQueue = 300000 * ConsumeQueue.CQStoreUnitSize;

也就是一个consumequeue文件存储30w个单元,每个单元20byte,也就是一个文件是:30w*20byte = 600wbyte
consumequeu的文件命名规则是:(n-1) * 600w,n是第几个文件。
即文件列表将是:

  1. 00000000000000000000
  2. 00000000000006000000
  3. 00000000000012000000
  4. 00000000000018000000
  5. 00000000000024000000
  6. 00000000000030000000
  7. (n-1) * 600w…

好了,有了以上条件后,如果拉取queue的offset是 600100

那么根据:每个存储单元占据20byte,则对应的文件索引是:60010020byte / (30w20byte)(每个文件大小),即第
3个文件,由于rocketmq有清理机制,即 第一个文件可能不存在,所以需要减掉已经清理的文件。来看下代码:

ConsumeQueue.getIndexBuffer()

/**
     * 返回Index Buffer
     * 
     * @param startIndex
     *            起始偏移量索引
     */
    public SelectMapedBufferResult getIndexBuffer(final long startIndex) {
        int mapedFileSize = this.mapedFileSize;
        long offset = startIndex * CQStoreUnitSize;
        if (offset >= this.getMinLogicOffset()) {
            MapedFile mapedFile = this.mapedFileQueue.findMapedFileByOffset(offset);
            if (mapedFile != null) {
                SelectMapedBufferResult result = mapedFile.selectMapedBuffer((int) (offset % mapedFileSize));
                return result;
            }
        }
        return null;
    }

MapedFileQueue.findMapedFileByOffset()

public MapedFile findMapedFileByOffset(final long offset, final boolean returnFirstOnNotFound) {
        try {
            this.readWriteLock.readLock().lock();
            MapedFile mapedFile = this.getFirstMapedFile();

            if (mapedFile != null) {
                //最关键的代码
                int index =
                        (int) ((offset / this.mapedFileSize) - (mapedFile.getFileFromOffset() / this.mapedFileSize));
                if (index < 0 || index >= this.mapedFiles.size()) {
                    logError
                        .warn(
                            "findMapedFileByOffset offset not matched, request Offset: {}, index: {}, mapedFileSize: {}, mapedFiles count: {}, StackTrace: {}",//
                            offset,//
                            index,//
                            this.mapedFileSize,//
                            this.mapedFiles.size(),//
                            UtilAll.currentStackTrace());
                }

                try {
                    return this.mapedFiles.get(index);
                }
                catch (Exception e) {
                    if (returnFirstOnNotFound) {
                        return mapedFile;
                    }
                }
            }
        }
        catch (Exception e) {
            log.error("findMapedFileByOffset Exception", e);
        }
        finally {
            this.readWriteLock.readLock().unlock();
        }

        return null;
    }

再来看第3个问题:
当消费者带tag订阅时,服务端只判断了hash是否一致,那当hash冲突的是在哪里处理的,来看下客户端代码:

DefaultMqPushConsumerImpl.pullMessage

 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()) {

pullAPIWrapper.processPullResult

public PullResult processPullResult(final MessageQueue mq, final PullResult pullResult,
            final SubscriptionData subscriptionData) {
        final String projectGroupPrefix = this.mQClientFactory.getMQClientAPIImpl().getProjectGroupPrefix();
        PullResultExt pullResultExt = (PullResultExt) pullResult;

        this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());
        if (PullStatus.FOUND == pullResult.getPullStatus()) {
            ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary());
            List<MessageExt> msgList = MessageDecoder.decodes(byteBuffer);

            List<MessageExt> msgListFilterAgain = msgList;
            if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()) {
                msgListFilterAgain = new ArrayList<MessageExt>(msgList.size());
                for (MessageExt msg : msgList) {
                    if (msg.getTags() != null) {
                        if (subscriptionData.getTagsSet().contains(msg.getTags())) {
                            msgListFilterAgain.add(msg);
                        }
                    }
                }
            }

            if (this.hasHook()) {
                FilterMessageContext filterMessageContext = new FilterMessageContext();
                filterMessageContext.setUnitMode(unitMode);
                filterMessageContext.setMsgList(msgListFilterAgain);
                this.executeHook(filterMessageContext);
            }

            if (!UtilAll.isBlank(projectGroupPrefix)) {
                subscriptionData.setTopic(VirtualEnvUtil.clearProjectGroup(subscriptionData.getTopic(),
                    projectGroupPrefix));
                mq.setTopic(VirtualEnvUtil.clearProjectGroup(mq.getTopic(), projectGroupPrefix));
                for (MessageExt msg : msgListFilterAgain) {
                    msg.setTopic(VirtualEnvUtil.clearProjectGroup(msg.getTopic(), projectGroupPrefix));

                    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET,
                        Long.toString(pullResult.getMinOffset()));
                    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET,
                        Long.toString(pullResult.getMaxOffset()));
                }
            }
            else {
                for (MessageExt msg : msgListFilterAgain) {
                    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET,
                        Long.toString(pullResult.getMinOffset()));
                    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET,
                        Long.toString(pullResult.getMaxOffset()));
                }
            }

            pullResultExt.setMsgFoundList(msgListFilterAgain);
        }

        pullResultExt.setMessageBinary(null);

        return pullResult;
    }

也就是在客户端再做了一层过滤,比对了tag真正的值
为什么要这么做呢?
tag 在服务端以hashcode的形式存储,这样是定长的,可以提高性能,为什么呢?看上面拉取消息的代码可以知道,根据offset能很迅速的定位到消息在哪一个文件、哪个位置,无需遍历

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值