【RocketMQ】TBW102主题的作用

1,605 阅读4分钟

1. 前言

在RocketMQ体系中,系统预定义了一系列的Topic,开发者自定义的Topic名称不可以和它们重名,否则会抛异常。这些预定义的系统Topic都有它们各自的用途,例如RMQ_SYS_TRANS_OP_HALF_TOPIC用来存放半事务消息,SCHEDULE_TOPIC_XXXX用来存放延时消息等等。其中有一个Topic较为特殊,它就是TBW102,它有什么用呢? ​

Producer在发送消息时,默认情况下,不需要提前创建好Topic,如果Topic不存在,Broker会自动创建Topic。但是新创建的Topic它的权限是什么?读写队列数是多少呢?这个时候就需要用到TBW102了,RocketMQ会基于该Topic的配置创建新的Topic。 ​

Broker通过属性autoCreateTopicEnable来设置是否开启自动创建Topic,如果配置为true,但是删除了TBW102,此时Producer发送一个不存在的Topic消息,Broker就会抛异常,这点需要注意。

2. 源码分析

在这里插入图片描述

2.1 Producer处理

Producer发送消息时,首先需要从NameServer拉取Topic路由信息,对应的方法是tryToFindTopicPublishInfo。因为是新Topic,NameServer必然不存在路由数据,所以第一次会拉取失败。不过没关系,Producer会进行第二次拉取。

private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
    // 先从本地缓存获取
    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
        // 没有缓存,或失效
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
        // 发请求从NameServer拉取,并更新缓存
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
    }

    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
        return topicPublishInfo;
    } else {
        // 不存在的Topic,第一次拉取不到,第二次通过TBW102拉取
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    }
}

第二次拉取时isDefault设为true,这表示它会拉取TBW102的路由数据,只要你没有人为的删除,那么它必然可以拉取成功。Producer会基于TBW102的配置构建TopicPublishInfo并缓存到本地,然后进行正常的消息发送。注意:此时Topic在Broker中仍然不存在!

TopicRouteData topicRouteData;
if (isDefault && defaultMQProducer != null) {
    // 不存在的Topic,第二次通过TBW102拉取
    topicRouteData = this.mQClientAPIImpl
        .getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),1000 * 3);
    if (topicRouteData != null) {
        for (QueueData data : topicRouteData.getQueueDatas()) {
            int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
            data.setReadQueueNums(queueNums);
            data.setWriteQueueNums(queueNums);
        }
    }
}

TopicRouteData设置好了,再调用topicRouteData2TopicPublishInfo()将其转换为TopicPublishInfo对象。因为是消息发送,所以Producer会根据writeQueueNums写队列数来创建对应数量的MessageQueue。 ​

Producer虽然基于TBW102的配置创建了新的Topic路由数据,但是在Broker上,该Topic并不存在。Producer发送消息时,会在Header里的defaultTopic属性带上TBW102,这样Broker发现消息Topic不存在时,会基于它自动创建新的Topic。

2.2 Broker处理

消息被正常发送到Broker后,会被SendMessageProcessor类处理。存储消息前,会先对消息做校验,方法是msgCheck()。 ​

消息校验时,一旦发现Topic不存在,会通过TopicConfigManager自动创建新的Topic。

// 从Map中取出Topic配置
TopicConfig topicConfig =
    this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
if (null == topicConfig) {
    // Topic不存在
    int topicSysFlag = 0;
    
    // 自动创建Topic
    topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageMethod(
        requestHeader.getTopic(),
        requestHeader.getDefaultTopic(),
        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
        requestHeader.getDefaultTopicQueueNums(), topicSysFlag);
    
    if (null == topicConfig) {
        // Topic创建失败
        response.setCode(ResponseCode.TOPIC_NOT_EXIST);
        response.setRemark("topic[" + requestHeader.getTopic() + "] not exist, apply first please!"
                           + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
        return response;
    }
}

Broker通过TopicConfigManager类管理Topic数据,存储在ConcurrentMap中,Broker这个时候会基于请求头里的defaultTopic去创建新的Topic,创建成功后会写入ConcurrentMap,然后发心跳给NameServer,将Topic注册上去,这样其它Producer就能感知到新的Topic了,方法是createTopicInSendMessageMethod()

topicConfig = this.topicConfigTable.get(topic);
if (topicConfig != null)
    // 已经存在,直接返回
    return topicConfig;
// 获取TBW102配置
TopicConfig defaultTopicConfig = this.topicConfigTable.get(defaultTopic);
if (defaultTopicConfig != null) {
    if (defaultTopic.equals(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC)) {
        if (!this.brokerController.getBrokerConfig().isAutoCreateTopicEnable()) {
            defaultTopicConfig.setPerm(PermName.PERM_READ | PermName.PERM_WRITE);
        }
    }
    // 基于TBW102创建新的TopicConfig
    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);
    // 写入本地缓存,发心跳给NameServer时就会注册新的Topic了
    this.topicConfigTable.put(topic, topicConfig);
    this.dataVersion.nextVersion();
    createNew = true;
    // 持久化到磁盘
    this.persist();
}

如果新的Topic创建成功,需要将其马上注册到NameServer中,这样其它Producer就能感知到了。

if (createNew) {
    // 创建了新的Topic,注册到Broker上
    this.brokerController.registerBrokerAll(false, true, true);
}

至此,一个全新的Topic被创建,后续就是正常的消息写入CommitLog的流程了。 ​

3. 总结

TBW102的作用是,当Producer发送一个不存在的Topic消息时,Broker可以基于它的配置自动创建新的Topic,新创建的Topic权限、读写队列数等均继承自TBW102,因此它的配置极为重要。不过一般情况下,还是推荐提前创建好Topic。 ​

整个流程总结一下,Producer发送一个不存在的Topic消息时,首先会从NameServer拉取Topic路由数据,第一次拉取必然失败,第二次会直接拉取TBW102的路由数据,基于它创建TopicPublishInfo并缓存到本地,进行正常的消息发送,在Header里将defaultTopic设置为TBW102。Broker接收到消息时,先对消息做Check,检查到Topic不存在,会基于defaultTopic的配置去创建该Topic,然后注册到NameServer上,这样一个全新的Topic就被自动创建了。