RocketMQ创建topic流程解析

本文详细介绍了RocketMQ中创建Topic的两种方式,包括自动创建和通过管理接口创建。重点讲解了MQAdminImpl.createTopic()方法的实现过程,包括获取broker路由、轮询创建、设置参数和调用MQClientAPIImpl接口。在Broker端,接收到创建请求后,会进行合法性检查、响应客户端、更新topic配置以及同步到NameServer。整个流程确保了Topic的正确创建和管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RocketMQ使用topic来分隔各个业务逻辑的消息,发送消息前需要创建topic。

topic的创建有两种方式,一种是broker支持在收发消息时自动创建,比如producer发过来的消息带了一个不存在的topic,如果broker设置成可自动创建的话,会自动尝试创建topic。
另外一种就是通过管理接口创建,这种方式生产环境用的更多一些,因为可以由管理员来统一管理topic。

客户端创建topic

RocketMQ提供了管理接口MQAdmin来支持用户的后台管理需求,比如topic创建,消息查询等。默认实现方法是MQAdminImpl.createTopic()

public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException {
        try {
            //1、一般使用defaultTopic获取已经存在的broker data,所有的broker默认都支持defaultTopic
            TopicRouteData topicRouteData = this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(key, timeoutMillis);
            List<BrokerData> brokerDataList = topicRouteData.getBrokerDatas();
            if (brokerDataList != null && !brokerDataList.isEmpty()) {
                Collections.sort(brokerDataList);

                boolean createOKAtLeastOnce = false;
                MQClientException exception = null;

                StringBuilder orderTopicString = new StringBuilder(); //没用到
                //2、轮询所有broker,在master上创建topic,中间有一个broker失败,则中止创建
                for (BrokerData brokerData : brokerDataList) {
                    String addr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
                    if (addr != null) {
                        TopicConfig topicConfig = new TopicConfig(newTopic);
                        //3、设置queue的数量
                        topicConfig.setReadQueueNums(queueNum);
                        topicConfig.setWriteQueueNums(queueNum);
                        //4、设置topic的属性,比如可读、可写
                        topicConfig.setTopicSysFlag(topicSysFlag);

                        boolean createOK = false;
                        for (int i = 0; i < 5; i++) {//重试4次
                            try {
                                this.mQClientFactory.getMQClientAPIImpl().createTopic(addr, key, topicConfig, timeoutMillis);
                                createOK = true;
                                createOKAtLeastOnce = true;
                                break;
                            } catch (Exception e) {
                                if (4 == i) {
                                    exception = new MQClientException("create topic to broker exception", e);
                                }
                            }
                        }

                        if (createOK) {
                            orderTopicString.append(brokerData.getBrokerName());
                            orderTopicString.append(":");
                            orderTopicString.append(queueNum);
                            orderTopicString.append(";");
                        }
                    }
                }

                if (exception != null && !createOKAtLeastOnce) {
                    throw exception;
                }
            } else {
                throw new MQClientException("Not found broker, maybe key is wrong", null);
            }
        } catch (Exception e) {
            throw new MQClientException("create new topic failed", e);
        }
    }

MQAdminImpl.createTopic中的参数如下:

key:这个参数是系统已经存在的一个topic的名称,新建的topic会跟它在相同的broker上创建
newTopic:新建的topic的唯一标识
queueNum:指定topic中queue的数量
topicSysFlag:topic的标记位设置,没有特殊要求就填0就可以了。可选值在TopicSysFlag中定义

函数MQAdminImpl.createTopic()的流程如下:

  • 第1步,根据提供的key代表的topic去获取broker的路由,如果想在所有broker创建,一般使用DefaultTopic,因为这个topic是在所有broker上都存在的。
  • 第2步,轮询所有的broker,在master上创建topic,中间有一个broker失败,则中止创建,返回失败。因为master和slave的配置数据也会自动同步,所以只需要在master上创建。
  • 第3,4步,设置参数
  • 第5步,调用MQClientAPIImpl接口创建,失败会重试4次。
MQClientAPIImpl#createTopic
public void createTopic(final String addr, final String defaultTopic, final TopicConfig topicConfig,
        final long timeoutMillis)
        throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
        CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader();
        requestHeader.setTopic(topicConfig.getTopicName());
        requestHeader.setDefaultTopic(defaultTopic);
        requestHeader.setReadQueueNums(topicConfig.getReadQueueNums());
        requestHeader.setWriteQueueNums(topicConfig.getWriteQueueNums());
        //设置topic的权限,可读,可写
        requestHeader.setPerm(topicConfig.getPerm());
        //设置topic支持的消息过滤类型
        requestHeader.setTopicFilterType(topicConfig.getTopicFilterType().name());
        requestHeader.setTopicSysFlag(topicConfig.getTopicSysFlag());
        //设置是否是顺序消息topic
        requestHeader.setOrder(topicConfig.isOrder());

        RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, requestHeader);

        RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr),
            request, timeoutMillis);
        assert response != null;
        switch (response.getCode()) {
            case ResponseCode.SUCCESS: {
                return;
            }
            default:
                break;
        }

        throw new MQClientException(response.getCode(), response.getRemark());
    }

函数createTopic就是设置好topic的参数然后发送RequestCode.UPDATE_AND_CREATE_TOPIC命令给Broker。后续的处理逻辑主要在Broker中。

Broker创建topic

Broker在接收到RequestCode.UPDATE_AND_CREATE_TOPIC命令后,进入函数AdminBrokerProcessor#updateAndCreateTopic

private synchronized RemotingCommand updateAndCreateTopic(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final CreateTopicRequestHeader requestHeader =
            (CreateTopicRequestHeader) request.decodeCommandCustomHeader(CreateTopicRequestHeader.class);
        log.info("updateAndCreateTopic called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
        //1、判断topicName的合法性,不能和clusterName同名
        if (requestHeader.getTopic().equals(this.brokerController.getBrokerConfig().getBrokerClusterName())) {
            String errorMsg = "the topic[" + requestHeader.getTopic() + "] is conflict with system reserved words.";
            log.warn(errorMsg);
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark(errorMsg);
            return response;
        }

        try {//2、先回复客户端创建成功,后更新broker缓存
            response.setCode(ResponseCode.SUCCESS);
            response.setOpaque(request.getOpaque());
            response.markResponseType();
            response.setRemark(null);
            ctx.writeAndFlush(response);
        } catch (Exception e) {
            log.error("Failed to produce a proper response", e);
        }

        TopicConfig topicConfig = new TopicConfig(requestHeader.getTopic());
        topicConfig.setReadQueueNums(requestHeader.getReadQueueNums());
        topicConfig.setWriteQueueNums(requestHeader.getWriteQueueNums());
        topicConfig.setTopicFilterType(requestHeader.getTopicFilterTypeEnum());
        topicConfig.setPerm(requestHeader.getPerm());
        topicConfig.setTopicSysFlag(requestHeader.getTopicSysFlag() == null ? 0 : requestHeader.getTopicSysFlag());
        //3、更新TopicConfigManager中的topic配置信息。不存在则创建,存在则更新,并且持久化到文件中
        this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig);
        //4、broker将topic信息同步到nameserv
        this.brokerController.registerIncrementBrokerData(topicConfig,this.brokerController.getTopicConfigManager().getDataVersion());
        return null;
    }

broker接收到RequestCode.UPDATE_AND_CREATE_TOPIC命令后,代码流程如下:

     1、判断topicName的合法性,不能和clusterName同名。

     2、先回复客户端创建成功,后更新broker缓存。

     3、更新TopicConfigManager中的topic配置信息。不存在则创建,存在则更新,并且持久化到文件中。

     4、broker将topic信息同步到nameserv。

在将topic保存后,broker会将新增的topic同步给NameServer,同步的过程跟broker注册是一样的。这样NameServer中就记录了topic的路由信息,后续发送消息的时候,客户端就可以从Broker中获取到Topic的路由信息。

RocketMQ 是一款高性能、高可靠的消息中间件,广泛应用于分布式系统中。其消息发送和消费流程是其核心机制之一,涵盖了消息的生产、存储、传输以及消费等多个环节。以下将对消息发送与消费流程进行详细解析。 ### 消息发送流程RocketMQ 中,消息发送支持三种方式:同步发送、异步发送和单向发送。以同步发送为例,发送流程如下: 1. **创建生产者(Producer)**:通过 `DefaultMQProducer` 类创建生产者,并设置生产者组名和 NameServer 地址,以便获取 Broker 的路由信息。 2. **启动生产者**:调用 `start()` 方法启动生产者,使其能够与 Broker 建立连接。 3. **构建消息**:使用 `Message` 类构建消息,指定 Topic、Tag 和消息体内容。 4. **发送消息**:调用 `send()` 方法将消息发送到 Broker。该方法会根据 Topic 查找对应的 Broker 地址,并将消息封装为请求发送到 Broker。 5. **返回发送结果**:发送完成后,Broker 会返回 `SendResult`,包含发送状态(如 `SEND_OK` 表示成功)。 6. **关闭生产者**:发送完成后,调用 `shutdown()` 方法关闭生产者。 示例代码如下: ```java import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; public class SyncProducerTest { public static void main(String[] args) throws Exception { // 1、创建 producer,设置组名为 SyncGroup DefaultMQProducer producer = new DefaultMQProducer("SyncGroup"); // 2、指定 NameServer 的地址,以获取 Broker 路由地址 producer.setNamesrvAddr("x.x.x.x:9876"); // 3、启动 producer producer.start(); // 4、创建消息,并指定 Topic,Tag 和消息体 Message msg = new Message("SyncTopic", "sync", "SyncMessage".getBytes("UTF-8")); // 5、发送同步消息 SendResult sendResult = producer.send(msg); // 6、通过 sendResult 判断消息是否成功送达 System.out.printf("message send result:" + sendResult); // 7、关闭 Producer producer.shutdown(); } } ``` ### 消息消费流程 RocketMQ 支持两种消费模式:集群消费(Clustering)和广播消费(Broadcasting)。在集群消费模式下,同一个消费组内的多个消费者实例会平均分配消息;而在广播模式下,每条消息会被发送给组内的所有消费者实例。 消费流程主要包括以下几个步骤: 1. **创建消费者(Consumer)**:通过 `DefaultMQPushConsumer` 创建消费者,并设置消费组名和 NameServer 地址。 2. **订阅 Topic**:调用 `subscribe()` 方法订阅需要消费的 Topic,并可设置 Tag 或 SQL 表达式进行消息过滤。 3. **注册消息监听器**:通过 `registerMessageListener()` 注册消息监听器,定义消息处理逻辑。 4. **启动消费者**:调用 `start()` 方法启动消费者,开始从 Broker 拉取消息。 5. **处理消息**:消费者从 Broker 拉取到消息后,执行消息监听器中的 `consumeMessage()` 方法处理消息。 6. **提交消费进度(Offset)**:处理完成后,消费者会将消费进度(Offset)提交到 Broker,以便下次消费时从上次的位置继续。 示例代码如下: ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.*; import org.apache.rocketmq.common.message.MessageExt; public class ConsumerTest { public static void main(String[] args) throws MQClientException { // 1、创建消费者,设置组名为 ConsumerGroup DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroup"); // 2、指定 NameServer 地址 consumer.setNamesrvAddr("x.x.x.x:9876"); // 3、订阅 Topic,并设置 * 表示订阅所有 Tag consumer.subscribe("SyncTopic", "*"); // 4、注册消息监听器 consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { for (MessageExt msg : msgs) { System.out.printf("Received message: %s%n", new String(msg.getBody())); } // 返回消费成功状态 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // 5、启动消费者 consumer.start(); System.out.println("Consumer started."); } } ``` 在消费过程中,如果消息处理失败,RocketMQ 提供了重试机制。消费者可以返回 `RECONSUME_LATER` 状态,表示消息需要重新投递。Broker 会将该消息放入重试队列,并在一定时间后再次投递给消费者。 ### 消息过滤机制 RocketMQ 支持通过 Tag 和 SQL 表达式对消息进行过滤。Tag 是消息的一个属性,消费者可以通过指定 Tag 来筛选特定类型的消息。SQL 表达式则允许消费者根据消息的属性(如自定义头信息)进行更复杂的过滤。 ### 事务消息 RocketMQ 还支持事务消息,适用于需要本地事务与消息发送保持一致性的场景。事务消息的发送流程如下: 1. **发送半消息(Half Message)**:生产者首先发送一条半消息到 Broker,该消息对消费者不可见。 2. **执行本地事务**:Broker 收到半消息后,生产者执行本地事务逻辑。 3. **提交或回滚消息**:事务执行成功后,生产者向 Broker 提交事务,消息变为可消费状态;若事务失败或超时,生产者回滚事务,消息被丢弃。 4. **事务回查**:若 Broker 未收到事务状态,会主动发起回查请求,询问生产者事务状态。 ### 存储机制 RocketMQ 的存储机制由 CommitLog 和 ConsumerQueue 组成。CommitLog 是消息的物理存储文件,所有消息按顺序写入其中,保证高吞吐量。ConsumerQueue 是消息的逻辑队列,记录了消息在 CommitLog 中的偏移量、大小和 Tag Hash 值,用于快速定位消息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值