Rocketmq source code analysis -- broker startup process

1. Startup inlet

The startup class of broker is org.apache.rocketmq.broker.BrokerStartup. The code is as follows:

public class BrokerStartup {
    ...

    public static void main(String[] args) {
        start(createBrokerController(args));
    }
    ...
}

In the main() method, there is only one line of code, which contains two operations:

  • createBrokerController(...): creates a BrokerController
  • start(...): starts the Broker

Next, let's analyze these two operations.

2. Create a BrokerController

The method to create the BrokerController is BrokerStartup#createBrokerController, and the code is as follows:

/**
 * Create the configuration parameters of the broker
 * @param args
 * @return
 */
public static BrokerController createBrokerController(String[] args) {
    ...

    try {
        //Parsing command line parameters
        Options options = ServerUtil.buildCommandlineOptions(new Options());
        commandLine = ServerUtil.parseCmdLine("mqbroker", args, buildCommandlineOptions(options),
            new PosixParser());
        if (null == commandLine) {
            System.exit(-1);
        }

        // Processing configuration
        final BrokerConfig brokerConfig = new BrokerConfig();
        final NettyServerConfig nettyServerConfig = new NettyServerConfig();
        final NettyClientConfig nettyClientConfig = new NettyClientConfig();

        // tls safety related
        nettyClientConfig.setUseTLS(Boolean.parseBoolean(System.getProperty(TLS_ENABLE,
            String.valueOf(TlsSystemConfig.tlsMode == TlsMode.ENFORCING))));
        // configure port
        nettyServerConfig.setListenPort(10911);
        // Configuration of message store
        final MessageStoreConfig messageStoreConfig = new MessageStoreConfig();

        ...
        // Set the configuration from the command line to the brokerConfig object
        MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), brokerConfig);

        // Check environment variable: ROCKETMQ_HOME
        if (null == brokerConfig.getRocketmqHome()) {
            System.out.printf("Please set the %s variable in your environment to match 
                the location of the RocketMQ installation", MixAll.ROCKETMQ_HOME_ENV);
            System.exit(-2);
        }

        //Omit some configuration
        ...

        // Create a brokerController
        final BrokerController controller = new BrokerController(
            brokerConfig,
            nettyServerConfig,
            nettyClientConfig,
            messageStoreConfig);
        controller.getConfiguration().registerConfig(properties);
        // initialization
        boolean initResult = controller.initialize();
        if (!initResult) {
            controller.shutdown();
            System.exit(-3);
        }
        // Close the hook and handle some operations before closing
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            private volatile boolean hasShutdown = false;
            private AtomicInteger shutdownTimes = new AtomicInteger(0);

            @Override
            public void run() {
                synchronized (this) {
                    if (!this.hasShutdown) {
                        ...
                        // A logout message will be sent to nameServer
                        controller.shutdown();
                        ...
                    }
                }
            }
        }, "ShutdownHook"));

        return controller;
    } catch (Throwable e) {
        e.printStackTrace();
        System.exit(-1);
    }

    return null;
}

The code of this method is a little long, but it does not have many functions. Generally speaking, there are three functions:

  1. Processing configuration: it mainly deals with the configuration of nettyServerConfig and nettyClientConfig. Here are some configuration resolution operations. The processing method is very similar to NameServer. I won't talk about it here.
  2. Create and initialize controller: call the method controller.initialize(), which we will analyze later.
  3. Register the close hook: call runtime. Getruntime(). Addshutdown hook (...) to perform some operations before the jvm process closes.

two point one   controller instantiation

The creation and initialization of the BrokerController are performed in the BrokerStartup#createBrokerController method. Let's first look at its construction method:

public BrokerController(
    final BrokerConfig brokerConfig,
    final NettyServerConfig nettyServerConfig,
    final NettyClientConfig nettyClientConfig,
    final MessageStoreConfig messageStoreConfig
) {
    // 4 core configuration information
    this.brokerConfig = brokerConfig;
    this.nettyServerConfig = nettyServerConfig;
    this.nettyClientConfig = nettyClientConfig;
    this.messageStoreConfig = messageStoreConfig;
    // Manage offset of consumer consumption message
    this.consumerOffsetManager = new ConsumerOffsetManager(this);
    // Manage topic configuration
    this.topicConfigManager = new TopicConfigManager(this);
    // Handling consumer pull message requests
    this.pullMessageProcessor = new PullMessageProcessor(this);
    this.pullRequestHoldService = new PullRequestHoldService(this);
    // Message delivery listener
    this.messageArrivingListener 
        = new NotifyMessageArrivingListener(this.pullRequestHoldService);
    ...
    // Components that send messages out
    this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig);
    ...
}

The construction method of BrokerController is very long. It is basically some assignment operations. Key items are listed in the code, including:

  • Core configuration assignment: mainly four configurations: brokerConfig/nettyServerConfig/nettyClientConfig/messageStoreConfig
  • ConsumerOffsetManager: manages the offset of the consumer's consumption message location. The offset represents the location where the consumer group consumes the topic message. When consuming later, it will consume from this location to avoid repeated consumption messages and missing consumption messages.
  • topicConfigManager: topic configuration manager, which is used to manage topic configuration, such as topic name and number of topic queues
  • pullMessageProcessor: a message processor used to handle messages pulled by consumers
  • messageArrivingListener: the listener for message delivery. When the producer's message is delivered, the listener will listen
  • Brokerouter API: a component that sends out messages, such as sending registration / logout messages to NameServer

The usefulness of the above components is mixed here, and we will analyze it later.

2.2 initialize controller

Let's take another look at the initialization operation. The method is BrokerController#initialize:

public boolean initialize() throws CloneNotSupportedException {
    // Load the configuration in the configuration file
    boolean result = this.topicConfigManager.load();
    result = result && this.consumerOffsetManager.load();
    result = result && this.subscriptionGroupManager.load();
    result = result && this.consumerFilterManager.load();

    if (result) {
        try {
            // The message storage management component manages messages on disk
            this.messageStore =
                new DefaultMessageStore(this.messageStoreConfig, this.brokerStatsManager, 
                    this.messageArrivingListener, this.brokerConfig);
            // When DLeger is enabled, DLeger related components are created
            if (messageStoreConfig.isEnableDLegerCommitLog()) {
                ...
            }
            // broker statistics component
            this.brokerStats = new BrokerStats((DefaultMessageStore) this.messageStore);
            //load plugin
            MessageStorePluginContext context = new MessageStorePluginContext(messageStoreConfig, 
                brokerStatsManager, messageArrivingListener, brokerConfig);
            this.messageStore = MessageStoreFactory.build(context, this.messageStore);
            this.messageStore.getDispatcherList().addFirst(
                new CommitLogDispatcherCalcBitMap(this.brokerConfig, this.consumerFilterManager));
        } catch (IOException e) {
            result = false;
            log.error("Failed to initialize", e);
        }
    }
    // Load the records on the disk, such as the location written by commitLog and the information of consumer subject / queue
    result = result && this.messageStore.load();

    if (result) {
        // Handling nettyServer
        this.remotingServer = new NettyRemotingServer(
            this.nettyServerConfig, this.clientHousekeepingService);
        NettyServerConfig fastConfig = (NettyServerConfig) this.nettyServerConfig.clone();
        fastConfig.setListenPort(nettyServerConfig.getListenPort() - 2);
        this.fastRemotingServer = new NettyRemotingServer(
            fastConfig, this.clientHousekeepingService);

        // Create thread pool start... Many types of thread pools will be created here
        ...
        // Thread pool that handles consumer pull operations
        this.pullMessageExecutor = new BrokerFixedThreadPoolExecutor(
            this.brokerConfig.getPullMessageThreadPoolNums(),
            this.brokerConfig.getPullMessageThreadPoolNums(),
            1000 * 60,
            TimeUnit.MILLISECONDS,
            this.pullThreadPoolQueue,
            new ThreadFactoryImpl("PullMessageThread_"));
        ...
        // Create thread pool

        // Register processor
        this.registerProcessor();

        // Start scheduled task start... Many scheduled tasks will be started here
        ...
        // The offset consumed by the consumer is persisted regularly, that is, the data is saved to the disk
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    BrokerController.this.consumerOffsetManager.persist();
                } catch (Throwable e) {
                    log.error("schedule persist consumerOffset error.", e);
                }
            }
        }, 1000 * 10, this.brokerConfig.getFlushConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
        ...
        // Start scheduled task end

        ...
        // Some operations of opening DLeger
        if (!messageStoreConfig.isEnableDLegerCommitLog()) {
            ...
        }
        // Process tls configuration
        if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
            ...
        }
        // Initialize some operations
        initialTransaction();
        initialAcl();
        initialRpcHooks();
    }
    return result;
}

This is still very long, and the key parts are annotated. The work done by this method is as follows:

  1. Load the configuration in the configuration file
  2. Assignment and initialization operations
  3. Create thread pool
  4. Register processor
  5. Start scheduled task

Here, let's take a look at the operation of this.registerProcessor():

1. Registered processor: BrokerController#registerProcessor

The method actually called by this.registerProcessor() is BrokerController#registerProcessor. The code is as follows:

public void registerProcessor() {
    /**
     * SendMessageProcessor
     */
    SendMessageProcessor sendProcessor = new SendMessageProcessor(this);
    sendProcessor.registerSendMessageHook(sendMessageHookList);
    sendProcessor.registerConsumeMessageHook(consumeMessageHookList);

    this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, 
        this.sendMessageExecutor);
    this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor,  
        this.sendMessageExecutor);
    this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, 
        this.sendMessageExecutor);
    this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, 
        this.sendMessageExecutor);
    ...

    /**
     * PullMessageProcessor
     */
    this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, 
        this.pullMessageExecutor);
    this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList);

    /**
        * ReplyMessageProcessor
        */
    ReplyMessageProcessor replyMessageProcessor = new ReplyMessageProcessor(this);
    replyMessageProcessor.registerSendMessageHook(sendMessageHookList);

    ...
}

There are many processors registered in this method. Only the contents related to messages are listed here, such as sending messages, replying messages, pulling messages, etc. these processors will be used later when processing producer/consumer messages. There is no analysis here.

two   remotingServer registration processor: nettyremoteingserver #registerprocessor

Let's take a look at the operation of the remotingServer registration processor. The method is nettyremoteingserver #registerprocessor:

public class NettyRemotingServer extends NettyRemotingAbstract implements RemotingServer {

    ...

    @Override
    public void registerProcessor(int requestCode, NettyRequestProcessor processor, 
            ExecutorService executor) {
        ExecutorService executorThis = executor;
        if (null == executor) {
            executorThis = this.publicExecutor;
        }

        Pair<NettyRequestProcessor, ExecutorService> pair = new Pair<NettyRequestProcessor, 
                ExecutorService>(processor, executorThis);
        this.processorTable.put(requestCode, pair);
    }

    ...
}

Finally, these processors are registered in the processorTable, which is a member variable of nettyremotengabstract and is defined as follows:

HashMap<Integer/* request code */, Pair<NettyRequestProcessor, ExecutorService>>

This is a hashMap structure. The key is code and the value is Pair. There are two member variables in this class: NettyRequestProcessor and ExecutorService. The mapping relationship between code and NettyRequestProcessor is stored in the hashMap.

2.3 register the close hook: runtime. Getruntime(). Addshutdown hook (...)

Next, let's take a look at the operation of registering and closing the hook:

// Close the hook and handle some operations before closing
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    private volatile boolean hasShutdown = false;
    private AtomicInteger shutdownTimes = new AtomicInteger(0);

    @Override
    public void run() {
        synchronized (this) {
            if (!this.hasShutdown) {
                ...
                // A logout message will be sent to nameServer
                controller.shutdown();
                ...
            }
        }
    }
}, "ShutdownHook"));

Follow up the BrokerController#shutdown method:

public void shutdown() {
    // Call the shutdown method of each component
    ...

    // Send logout message to NameServer
    this.unregisterBrokerAll();
    ...
    // Consumption offset of persistent consumer
    this.consumerOffsetManager.persist();

    // It also calls the shutdown method of each component
    ...

}

In this method, the shutdown() method of each component will be called, the logoff message will be sent to the NameServer, and the consumption offset of the consumer will be persisted. Here we mainly look at the method of sending the logoff message BrokerController#unregisterBrokerAll:

private void unregisterBrokerAll() {
    // Send a logout message to nameServer
    this.brokerOuterAPI.unregisterBrokerAll(
        this.brokerConfig.getBrokerClusterName(),
        this.getBrokerAddr(),
        this.brokerConfig.getBrokerName(),
        this.brokerConfig.getBrokerId());
}

Continue to the brokerouter API #unregisterbrokerall:

public void unregisterBrokerAll(
    final String clusterName,
    final String brokerAddr,
    final String brokerName,
    final long brokerId
) {
    // Get all nameservers, traverse and send logout messages
    List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
    if (nameServerAddressList != null) {
        for (String namesrvAddr : nameServerAddressList) {
            try {
                this.unregisterBroker(namesrvAddr, clusterName, brokerAddr, brokerName, brokerId);
                log.info("unregisterBroker OK, NamesrvAddr: {}", namesrvAddr);
            } catch (Exception e) {
                log.warn("unregisterBroker Exception, {}", namesrvAddr, e);
            }
        }
    }
}

In this method, all nameservers will be obtained, then logout messages will be sent one by one, and continue to enter the brokerouter api#unregisterbroker method:

public void unregisterBroker(
    final String namesrvAddr,
    final String clusterName,
    final String brokerAddr,
    final String brokerName,
    final long brokerId
) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, 
        InterruptedException, MQBrokerException {
    UnRegisterBrokerRequestHeader requestHeader = new UnRegisterBrokerRequestHeader();
    requestHeader.setBrokerAddr(brokerAddr);
    requestHeader.setBrokerId(brokerId);
    requestHeader.setBrokerName(brokerName);
    requestHeader.setClusterName(clusterName);
    // Logout message sent: RequestCode.UNREGISTER_BROKER
    RemotingCommand request = RemotingCommand.createRequestCommand(
            c, requestHeader);

    RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, 3000);
    assert response != null;
    switch (response.getCode()) {
        case ResponseCode.SUCCESS: {
            return;
        }
        default:
            break;
    }

    throw new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr);
}

Finally, RemotingClient#invokeSync is called to send messages, and the request code is RequestCode.UNREGISTER_BROKER, which corresponds to the logout message received by the NameServer from the broker.

3. Start the Broker: start(...)

Let's take another look at the Broker startup process. The processing method is BrokerController#start:

public void start() throws Exception {
    // Start each component

    // Start message store related components
    if (this.messageStore != null) {
        this.messageStore.start();
    }

    // Starting remoting server is actually starting a netty service to receive messages from producer
    if (this.remotingServer != null) {
        this.remotingServer.start();
    }

    ...

    // broker is a component that sends messages to the outside world. It is used when reporting survival messages to nameServer. It is also a netty service
    if (this.brokerOuterAPI != null) {
        this.brokerOuterAPI.start();
    }

    ...

    // Heartbeat registration task of broker core
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            try {
                BrokerController.this.registerBrokerAll(true, false, 
                    brokerConfig.isForceRegister());
            } catch (Throwable e) {
                log.error("registerBrokerAll Exception", e);
            }
        }
        // The value of brokerConfig.getRegisterNameServerPeriod() is 1000 * 30. The final calculation is executed every 30 seconds by default
    }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), 
            TimeUnit.MILLISECONDS);

    ...

}

This method is mainly to start each component. Here are several important components:

  1. messageStore: message storage component. In this component, threads related to message storage will be started, such as message delivery operation, flush operation of commitLog file, flush operation of comsumeQueue file, etc
  2. remotingServer: netty service, which is used to receive request messages, such as messages sent by producer
  3. Brokerouter API: it is also a netty service, which is used to send messages externally, such as reporting heartbeat messages to nameServer
  4. Start scheduled task: the broker sends a registration message to the nameServer

Here we focus on how the scheduled task sends the heartbeat.

The time interval for processing registration message sending is as follows:

Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)

This line of code looks long, but the meaning is just one sentence: the time interval can be configured by itself, but it can not be less than 10s or greater than 60s. The default is 30s

The method of message registration is BrokerController#registerBrokerAll(...), and the code is as follows:

public synchronized void registerBrokerAll(final boolean checkOrderConfig, 
        boolean oneway, boolean forceRegister) {
    TopicConfigSerializeWrapper topicConfigWrapper 
            = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
    // Handle topic related configuration
    if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())
        || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
        ...
    }
    // Whether registration is required will be determined here
    if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
        this.getBrokerAddr(),
        this.brokerConfig.getBrokerName(),
        this.brokerConfig.getBrokerId(),
        this.brokerConfig.getRegisterBrokerTimeoutMills())) {
        // Register    
        doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
    }
}

This method is used to handle the registration operation. However, before registration, it will verify whether registration is required. The method to verify whether registration is required is BrokerController#needRegister. The code is as follows:

private boolean needRegister(final String clusterName,
    final String brokerAddr,
    final String brokerName,
    final long brokerId,
    final int timeoutMills) {

    TopicConfigSerializeWrapper topicConfigWrapper 
        = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
    // Determine whether registration is required
    List<Boolean> changeList = brokerOuterAPI.needRegister(clusterName, brokerAddr, brokerName, 
        brokerId, topicConfigWrapper, timeoutMills);
    // If there is a change, it means that you need to register    
    boolean needRegister = false;
    for (Boolean changed : changeList) {
        if (changed) {
            needRegister = true;
            break;
        }
    }
    return needRegister;
}

This method calls the brokerouter API. Needregister (...) to determine whether the broker has changed. As long as there is a change on a NameServer, it indicates that registration is required.

How does the broker outer API. Needregister (...) determine whether the broker has changed? Follow up with brokerouter API #needregister:

public List<Boolean> needRegister(
    final String clusterName,
    final String brokerAddr,
    final String brokerName,
    final long brokerId,
    final TopicConfigSerializeWrapper topicConfigWrapper,
    final int timeoutMills) {
    final List<Boolean> changedList = new CopyOnWriteArrayList<>();
    // Get all nameservers
    List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
    if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
        final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
        // Traverse all nameservers and send requests one by one
        for (final String namesrvAddr : nameServerAddressList) {
            brokerOuterExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        QueryDataVersionRequestHeader requestHeader 
                            = new QueryDataVersionRequestHeader();
                        ...
                        // Send a message to nameServer. The command is RequestCode.QUERY_DATA_VERSION
                        RemotingCommand request = RemotingCommand
                            .createRequestCommand(RequestCode.QUERY_DATA_VERSION, requestHeader);
                        // Send the current DataVersion to nameServer     
                        request.setBody(topicConfigWrapper.getDataVersion().encode());
                        // Send request to nameServer
                        RemotingCommand response = remotingClient
                            .invokeSync(namesrvAddr, request, timeoutMills);
                        DataVersion nameServerDataVersion = null;
                        Boolean changed = false;
                        switch (response.getCode()) {
                            case ResponseCode.SUCCESS: {
                                QueryDataVersionResponseHeader queryDataVersionResponseHeader =
                                  (QueryDataVersionResponseHeader) response
                                  .decodeCommandCustomHeader(QueryDataVersionResponseHeader.class);
                                changed = queryDataVersionResponseHeader.getChanged();
                                byte[] body = response.getBody();
                                if (body != null) {
                                    // Get DataVersion
                                    nameServerDataVersion = DataVersion.decode(body, D
                                        ataVersion.class);
                                    // Here is the key to judgment
                                    if (!topicConfigWrapper.getDataVersion()
                                        .equals(nameServerDataVersion)) {
                                        changed = true;
                                    }
                                }
                                if (changed == null || changed) {
                                    changedList.add(Boolean.TRUE);
                                }
                            }
                            default:
                                break;
                        }
                        ...
                    } catch (Exception e) {
                        ...
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            });

        }
        try {
            countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error("query dataversion from nameserver countDownLatch await Exception", e);
        }
    }
    return changedList;
}

In this method, first traverse all nameservers and send a code of requestcode.query to each nameServer_ DATA_ Version parameter. The parameter is the DataVersion of the current broker. When the nameServer receives the message, it returns the DataVersion saved in the nameServer and corresponding to the current broker. When the two versions are not equal, it indicates that the current broker has changed and needs to be re registered.

What is DataVersion? Some of its codes are as follows:

public class DataVersion extends RemotingSerializable {
    // time stamp
    private long timestamp = System.currentTimeMillis();
    // Counter, which can be understood as the latest version number
    private AtomicLong counter = new AtomicLong(0);

    public void nextVersion() {
        this.timestamp = System.currentTimeMillis();
        this.counter.incrementAndGet();
    }

    /**
     * equals Method. When timestamp and counter are equal, they are equal
     */
    @Override
    public boolean equals(final Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final DataVersion that = (DataVersion) o;

        if (timestamp != that.timestamp) {
            return false;
        }

        if (counter != null && that.counter != null) {
            return counter.longValue() == that.counter.longValue();
        }

        return (null == counter) && (null == that.counter);
    }
    ...
} 

From the equals() method of DataVersion, two DataVersion objects are equal only when timestamp and counter are equal. Where will these two values be modified? From the call of DataVersion#nextVersion method, there are two main changes in these two values:

  • broker   A new one was created on   topic
  • topic has changed

In both cases, the DataVersion#nextVersion method is called, causing the DataVersion to change. When the DataVersion changes, it indicates that the current broker needs to register with the nameServer.

Let's go back to the BrokerController#registerBrokerAll(...) method:

public synchronized void registerBrokerAll(final boolean checkOrderConfig, 
        boolean oneway, boolean forceRegister) {
    ...
    // Whether registration is required will be determined here
    if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
        this.getBrokerAddr(),
        this.brokerConfig.getBrokerName(),
        this.brokerConfig.getBrokerId(),
        this.brokerConfig.getRegisterBrokerTimeoutMills())) {
        // Register    
        doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
    }
}

The registration method is BrokerController#doRegisterBrokerAll. Take a look at its process:

private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,
        TopicConfigSerializeWrapper topicConfigWrapper) {
    // register
    List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(
        this.brokerConfig.getBrokerClusterName(),
        this.getBrokerAddr(),
        this.brokerConfig.getBrokerName(),
        this.brokerConfig.getBrokerId(),
        this.getHAServerAddr(),
        // This object contains the version information of the current broker
        topicConfigWrapper,
        this.filterServerManager.buildNewFilterServerList(),
        oneway,
        this.brokerConfig.getRegisterBrokerTimeoutMills(),
        this.brokerConfig.isCompressedRegister());

    ...
}

Continue, and finally call the brokerouter api#registerbroker method:

private RegisterBrokerResult registerBroker(
    final String namesrvAddr,
    final boolean oneway,
    final int timeoutMills,
    final RegisterBrokerRequestHeader requestHeader,
    final byte[] body
) throws RemotingCommandException, MQBrokerException, RemotingConnectException, 
    RemotingSendRequestException, RemotingTimeoutException, InterruptedException {
    // Build request
    RemotingCommand request = RemotingCommand
        .createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);
    request.setBody(body);
    // Processing send operation: sendOneWay
    if (oneway) {
        try {
            // Registration operation
            this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);
        } catch (RemotingTooMuchRequestException e) {
            // Ignore
        }
        return null;
        ...
    }
    ....
}
        

Therefore, the so-called registration operation is when the nameServer sends a code as requestcode.register_ The broker message will bring the topic information and version number of the current broker.

4. Summary

This paper mainly analyzes the broker startup process. Generally speaking, the startup process is divided into three parts:

  1. Parse the configuration file. This step will parse various configurations and assign them to the corresponding objects
  2. BrokerController creation and initialization: the BrokerController object is created and initialized. The so-called initialization is to load the configuration in the configuration file, create a thread pool, register the request processor, start scheduled tasks, etc
  3. Broker controller startup: this step is to start the core components of the broker, such as messagestore (message store), remotingServer(netty service, which is used to process producer and consumer requests), broker outer API (netty service, which is used to report the current broker information to nameServer), etc.

In the analysis and startup process, two types of messages are mainly analyzed:

  1. In the shutdown hook, the broker will send a logout message to the nameServer, which indicates that the nameServer will clear the registration information of the current broker before the broker is closed
  2. After the broker is started, it will start a scheduled task to regularly determine whether it needs to register with nameServer. When it determines whether it needs to register, it will send code query to nameServer_ DATA_ Version message: get the version number of the current broker from the nameServer. If the version number is inconsistent with the local version number, it means that you need to re register with the broker, that is, send a registration message.

Tags: Java

Posted on Thu, 02 Sep 2021 03:40:01 -0400 by JoelyUK