10. topic的自动创建流程

为什么官方建议:

自动创建topic机制,建议线下开启,线上关闭。

rocketmq在发送消息时,会先去获取topic的路由信息,如果topic是第一次发送消息,由于nameserver没有topic的路由信息,所以会再次以“TBW102”这个默认topic获取路由信息,假设broker都开启了自动创建开关,那么此时会获取所有broker的路由信息,消息的发送会根据负载算法选择其中一台Broker发送消息,消息到达broker后,发现本地没有该topic,会在创建该topic的信息塞进本地缓存中,同时会将topic路由信息注册到nameserver中,那么这样就会造成一个后果:以后所有该topic的消息,都将发送到这台broker上,如果该topic消息量非常大,会造成某个broker上负载过大,这样的消息存储就达不到负载均衡的效果了。
————————————————
版权声明:本文为CSDN博主「后端进阶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zchdjb/article/details/90795265

1、producer 生产消息

producer在发送消息之前,会先去nameSrv拉取topic信息(TopicPublishInfo),

TopicPublishInfo

MessageQueue

TopicRouteData

QueueData

BrokerData

 以上数据结构的分析如下:

QueueData,一个逻辑上的概念,代表了一个topic的所拥有的queue信息,

比如在broker-A上有2个读队列,2个写队列

MessageQueue就是实际的队列了,

一个QueueData相当于描述了多个MessageQueue,取决于QueueData属性readQueueNums,writeQueueNums

参考

简单梳理Topic,MessageQueue,QueueData,broker之间的关系 - 简书

调用tryToFindTopicPublishInfo()获取TopicPublishInfo

  private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
        // 先尝试从本地获取topic信息
        TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
        // 本地获取为空,或者messageQueue为空
        if (null == topicPublishInfo || !topicPublishInfo.ok()) {
            // 保存一个空的topicPublishInfo
            this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
            // 从namesrv拉取topic信息,包含broker的地址
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
            // 重新复制topicPublishInfo
            topicPublishInfo = this.topicPublishInfoTable.get(topic);
        }

        if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
            // topicPublishInfo 可用时,直接返回
            return topicPublishInfo;
        } else {
            // topicPublishInfo不可用
            // 更新topic信息,但是传递了isDefault=true
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
            // 返回的是topic的真实信息,或者是默认topic(TBW102)的配置信息(修改了queueNums)
            topicPublishInfo = this.topicPublishInfoTable.get(topic);
            return topicPublishInfo;
        }
    }

(1)先从本地获取topicPublishInfo,

(2)本地获取不到时,尝试从nameSrv获取 

(3)如果从nameSrv获取到时,直接返回,获取不到时,会再次调用updateTopicRouteInfoFFromNameServer(), 但是传递了isDefault=true,

会走updateTopicRouteInfoFromNameServer()中的代码如下:

if (isDefault && defaultMQProducer != null) {
                        // 获取默认topic=TBW102的路由信息
                        topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),
                            1000 * 3);
                        if (topicRouteData != null) {
                            for (QueueData data : topicRouteData.getQueueDatas()) {
                                // defaultTopicQueueNums = 4;
                                // data.getReadQueueNums = 8
                                // 取较小的一个,然后每个queueData都重新赋值
                                int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
                                data.setReadQueueNums(queueNums);
                                data.setWriteQueueNums(queueNums);
                            }
                        }
                    }

会从nameSrv中获取默认自动创建的TBW102的topicRoute信息,然后可能会修改readQueueNums和writeQueueNums数量

然后会将topicRuteData封装成TopicPublishInfo,保存在变量producerTable中,

然后在外层topicPublishInfoTable中,就可以获取到topic对应的topicPublishInfo了,

然后在获取到的topicPublishInfo中,先选取一个broker,再在broker上选取一个messageQueue,随后将message发送到这个选中的messageQueue上。

注意:上面获取的topicRoute信息,里面的broker信息,可能不是集群的所有broker信息,虽然是默认TBW102的所有broker信息,但是只有开启了自动创建topic的broker,在初始化的时候,才会去初始化默认TBW102的topic信息。

BrokerStartup.createBrokerController()-->new BrokerController()--> new TopicConfigManager()

在TopicConfigManager的构造函数中

{
            // broker配置了autoCreateTopicEnable=true
            // 自动创建topic
            if (this.brokerController.getBrokerConfig().isAutoCreateTopicEnable()) {
                String topic = TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC;
                TopicConfig topicConfig = new TopicConfig(topic);
                TopicValidator.addSystemTopic(topic);
                // 读队列,默认8个
                topicConfig.setReadQueueNums(this.brokerController.getBrokerConfig()
                    .getDefaultTopicQueueNums());
                // 写队列,默认8个
                topicConfig.setWriteQueueNums(this.brokerController.getBrokerConfig()
                    .getDefaultTopicQueueNums());
                // 读写信息
                int perm = PermName.PERM_INHERIT | PermName.PERM_READ | PermName.PERM_WRITE;
                topicConfig.setPerm(perm);
                // 将自动创建的topic信息保存到topicConfigTable
                this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig);
            }
        }

2. broker处理broker发送的消息

前面分析过,producer生产消息,发送的消息类型是SEND_MESSAGE_V2,broker服务上,经由sendMessageProcessor来处理

经由asyncSendMessage()方法来处理,回先调用preSend()方法

    private RemotingCommand preSend(ChannelHandlerContext ctx, RemotingCommand request,
                                    SendMessageRequestHeader requestHeader) {
        final RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);

        response.setOpaque(request.getOpaque());

        response.addExtField(MessageConst.PROPERTY_MSG_REGION, this.brokerController.getBrokerConfig().getRegionId());
        response.addExtField(MessageConst.PROPERTY_TRACE_SWITCH, String.valueOf(this.brokerController.getBrokerConfig().isTraceOn()));

        log.debug("Receive SendMessage request command {}", request);

        final long startTimestamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp();

        if (this.brokerController.getMessageStore().now() < startTimestamp) {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark(String.format("broker unable to service, until %s", UtilAll.timeMillisToHumanString2(startTimestamp)));
            return response;
        }

        response.setCode(-1);
        // 调用父类的msgCheck()方法
        super.msgCheck(ctx, requestHeader, response);
        if (response.getCode() != -1) {
            return response;
        }

        return response;
    }

--


    protected RemotingCommand msgCheck(final ChannelHandlerContext ctx,
        final SendMessageRequestHeader requestHeader, final RemotingCommand response) {
        if (!PermName.isWriteable(this.brokerController.getBrokerConfig().getBrokerPermission())
            && this.brokerController.getTopicConfigManager().isOrderTopic(requestHeader.getTopic())) {
            response.setCode(ResponseCode.NO_PERMISSION);
            response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
                + "] sending message is forbidden");
            return response;
        }

        if (!TopicValidator.validateTopic(requestHeader.getTopic(), response)) {
            return response;
        }
        if (TopicValidator.isNotAllowedSendTopic(requestHeader.getTopic(), response)) {
            return response;
        }

        TopicConfig topicConfig =
            this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
        if (null == topicConfig) {
            int topicSysFlag = 0;
            if (requestHeader.isUnitMode()) {
                if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    topicSysFlag = TopicSysFlag.buildSysFlag(false, true);
                } else {
                    topicSysFlag = TopicSysFlag.buildSysFlag(true, false);
                }
            }

            log.warn("the topic {} not exist, producer: {}", requestHeader.getTopic(), ctx.channel().remoteAddress());
            // 调用创建topic方法
            topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageMethod(
                requestHeader.getTopic(),
                requestHeader.getDefaultTopic(),
                RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                requestHeader.getDefaultTopicQueueNums(), topicSysFlag);
            //
            if (null == topicConfig) {
                if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    topicConfig =
                        this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(
                            requestHeader.getTopic(), 1, PermName.PERM_WRITE | PermName.PERM_READ,
                            topicSysFlag);
                }
            }

            if (null == topicConfig) {
                response.setCode(ResponseCode.TOPIC_NOT_EXIST);
                response.setRemark("topic[" + requestHeader.getTopic() + "] not exist, apply first please!"
                    + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
                return response;
            }
        }

        int queueIdInt = requestHeader.getQueueId();
        int idValid = Math.max(topicConfig.getWriteQueueNums(), topicConfig.getReadQueueNums());
        if (queueIdInt >= idValid) {
            String errorInfo = String.format("request queueId[%d] is illegal, %s Producer: %s",
                queueIdInt,
                topicConfig.toString(),
                RemotingHelper.parseChannelRemoteAddr(ctx.channel()));

            log.warn(errorInfo);
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark(errorInfo);

            return response;
        }
        return response;
    }

--

    public TopicConfig createTopicInSendMessageMethod(final String topic, final String defaultTopic,
        final String remoteAddress, final int clientDefaultTopicQueueNums, final int topicSysFlag) {
        TopicConfig topicConfig = null;
        boolean createNew = false;

        try {
            if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
                try {
                    topicConfig = this.topicConfigTable.get(topic);
                    // 获取到topicConfig不为空,直接返回
                    if (topicConfig != null)
                        return topicConfig;

                    //获取不到topicConfig
                    // 获取默认topic的topicConfig
                    TopicConfig defaultTopicConfig = this.topicConfigTable.get(defaultTopic);
                    if (defaultTopicConfig != null) {
                        // 判断默认topic是TBW102
                        if (defaultTopic.equals(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC)) {
                            // broker配置autoCreateTopicEnable=false
                            // 就设置读写模式为可读可写
                            if (!this.brokerController.getBrokerConfig().isAutoCreateTopicEnable()) {
                                defaultTopicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE);
                            }
                        }
                        // 默认topic读写模式为继承
                        if (PermName.isInherited(defaultTopicConfig.getPerm())) {
                            topicConfig = new TopicConfig(topic);

                            int queueNums = Math.min(clientDefaultTopicQueueNums, defaultTopicConfig.getWriteQueueNums());

                            if (queueNums < 0) {
                                queueNums = 0;
                            }

                            topicConfig.setReadQueueNums(queueNums);
                            topicConfig.setWriteQueueNums(queueNums);
                            int perm = defaultTopicConfig.getPerm();
                            perm &= ~PermName.PERM_INHERIT;
                            topicConfig.setPerm(perm);
                            topicConfig.setTopicSysFlag(topicSysFlag);
                            topicConfig.setTopicFilterType(defaultTopicConfig.getTopicFilterType());
                        } else {
                            log.warn("Create new topic failed, because the default topic[{}] has no perm [{}] producer:[{}]",
                                defaultTopic, defaultTopicConfig.getPerm(), remoteAddress);
                        }
                    } else {
                        log.warn("Create new topic failed, because the default topic[{}] not exist. producer:[{}]",
                            defaultTopic, remoteAddress);
                    }

                    if (topicConfig != null) {
                        log.info("Create new topic by default topic:[{}] config:[{}] producer:[{}]",
                            defaultTopic, topicConfig, remoteAddress);
                        // 将topicconfig保存在本地缓存中
                        this.topicConfigTable.put(topic, topicConfig);

                        this.dataVersion.nextVersion();

                        createNew = true;

                        this.persist();
                    }
                } finally {
                    this.topicConfigTableLock.unlock();
                }
            }
        } catch (InterruptedException e) {
            log.error("createTopicInSendMessageMethod exception", e);
        }

        if (createNew) {
            // 新创建的topic,立即上报broker信息,将topic同步给namesrv
            this.brokerController.registerBrokerAll(false, true, true);
        }

        return topicConfig;
    }

之后会将topicconfig保存在本地缓存中,如果是新创建的topic,就立即向nameSrv发送一下broker信息,到这里,topic已经创建完成

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值