Namesrv for RocketMQ Source Analysis

Keywords: Java RocketMQ

Namesrv for RocketMQ Source Analysis

Source analysis is more content points, of course, we can not read all the source code of RocketMQ, so we interpret the core, focus source code.

The source code to be interpreted is as follows:

1,RocketMQ Overall architecture of source analysis
2,RocketMQ Source Analysis NameServer
3,RocketMQ Source Analysis Remoting
4,RocketMQ Source Analysis Producer
5,RocketMQ Source Analysis Store
6,RocketMQ Source Analysis Consumer
7,RocketMQ Memory Mapping in Source Analysis
8,RocketMQ Data synchronization and asynchronization mechanisms for source analysis
9,RocketMQ Transaction messages for source analysis
10,RocketMQ Message filtering and retry for source analysis

Look at the source with questions

  1. Connectivity of NameServer, Broker, Producer, Consumer

  2. What does it matter when Producer and Consumer connections are established?

  3. What information does NameServer store and how?

  4. Is Topic's persistent storage in NameServer or Broker?

RocketMQ Overall Architecture and Connectivity

The figure below is a typical two-master-two-slave architecture that includes producers and consumers.


They are not communicating with each other in the NameServer cluster.

  • Producers establish long connections to one of the NameServer clusters at the same time.

  • All Master Broker s between producers and brokers maintain long connections.

  • Consumers establish long connections to one of the NameServer clusters at the same time.

  • Consumers make long connections to all brokers.

RocketMQ Core Components and Overall Process

rocketmq-all-4.8.0-source-release
    .github
    acl
    broker
    client
    common
    dev
    distribution
    docs
    example
    filter
    logappender
    logging
    namesrv
    openmessaging
    remoting
    srvutil
    store
    style
    target
    test
    tools

The source code for RocketMQ looks a lot, but if it's broken down by components, there are only a few cores. As follows:

  • NameServer#start

  • BrokerStartup#main

  • DefaultMQProducerImpl#start

  • DefaultMQProducerImpl#send(Message)

  • DefaultMQPushConsumerImpl#start

NameServer

Name the service, update and route the discovery broker service.

NameServer is designed to provide routing information about Topic to message producers and consumers. In addition to storing the basic routing information, NameServer can also manage

Broker node, including routing registration, routing deletion, and so on.

Producer and Constumer

The java version of the mq client implementation, including producers and consumers.

Broker

It receives requests from producer s and consumer s and calls the store-tier service to process messages. The basic unit of the HA service, which supports synchronous and asynchronous dual-write modes.

Store

Storage layer implementation, including index services, highly available HA service implementation.

NettyRemotingServer and NettyRemotingClient

Based on netty's underlying communication implementation, all service interactions are based on this module. It also distinguishes between server and client.

NameServer Source Analysis

NameServer Startup Process Summary


Start process

From the startup of the source code, NameServer starts separately.

	Entry class:**NamesrvController**(NamesrvStartup)

	Core approach:**NamesrvController** In class **main()->main0-> createNamesrvController->start() -> initialize()**

Step One

Parse the configuration file, populate NameServerConfig, NettyServerConfig attribute values, and create NamesrvController

	NamesrvController In class createNamesrvController Method
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
    System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
    //PackageConflictDetect.detectFastjson();
    //Create NamesrvConfig
    final NamesrvConfig namesrvConfig = new NamesrvConfig();
    //Create NettyServerConfig
    final NettyServerConfig nettyServerConfig = new NettyServerConfig();
    //Set Startup Port Number
    nettyServerConfig.setListenPort(9876);
    //Resolve Start-c Parameter
    if (commandLine.hasOption('c')) {
        String file = commandLine.getOptionValue('c');
        if (file != null) {
            InputStream in = new BufferedInputStream(new FileInputStream(file));
            properties = new Properties();
            properties.load(in);
            MixAll.properties2Object(properties, namesrvConfig);
            MixAll.properties2Object(properties, nettyServerConfig);

            namesrvConfig.setConfigStorePath(file);

            System.out.printf("load config properties file OK, %s%n", file);
            in.close();
        }
    }
    //Resolve Start-p Parameter
    if (commandLine.hasOption('p')) {
        InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
        MixAll.printObjectProperties(console, namesrvConfig);
        MixAll.printObjectProperties(console, nettyServerConfig);
        System.exit(0);
    }
    //Fill in startup parameters to namesrvConfig,nettyServerConfig
    MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);

    log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);

    MixAll.printObjectProperties(log, namesrvConfig);
    MixAll.printObjectProperties(log, nettyServerConfig);
    //todo creates NameServerController
    final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);

    // remember all configs to prevent discard
    controller.getConfiguration().registerConfig(properties);

    return controller;
} // With deletion

Here you can see that if you want to see the various parameters, you can print all the parameter information for this NameServer by simply entering -p in the startup parameter

Similarly, all parameters must be found in the boot log:

Step 2

Create and initialize a NamesrvController instance based on the startup properties. The NameServerController instance is the NameServer core controller. The core controller starts the timer task:

	1, Every 10 s Scan once Broker,Remove inactive Broker

2. Print KV configuration every 10 minutes

initialize() in the NamesrvController class

public boolean initialize() {
    //Load KV Configuration
    this.kvConfigManager.load();
    //Create NettyServer Network Processing Object
    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
    //Turn on the timer task: scan the Broker every 10s to remove inactive brokers
    this.remotingExecutor = Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), 
                                                         new ThreadFactoryImpl("RemotingExecutorThread_"));
    this.registerProcessor();
    //Scan every 10s as active Broker
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            //todo scans every 10s as active Broker
            NamesrvController.this.routeInfoManager.scanNotActiveBroker();
        }
    }, 5, 10, TimeUnit.SECONDS);
    //Turn on timer tasks: Print KV configuration every 10 minutes
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            NamesrvController.this.kvConfigManager.printAllPeriodically();
        }
    }, 1, 10, TimeUnit.MINUTES);
    ...
    return true;
}

Step Three

Close the thread pool and release resources in time before the JVM process closes

//Hook Method
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
    @Override
    public Void call() throws Exception {
        controller.shutdown();
        return null;
    }
}));

Broker Startup Process Summary

Entry class: BrokerController(BrokerStartup)

Core methods:

Broker sends a message to NameServer

//todo broker sends heartbeat packets to NameServer every 30s
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        try {
            //todo sends a heartbeat packet to NameServer
            BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
        } catch (Throwable e) {
            log.error("registerBrokerAll Exception", e);
        }
    }
}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
    TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
...
    //todo sends heartbeat packets to all servers (NameServer)
    if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
                                      this.getBrokerAddr(),
                                      this.brokerConfig.getBrokerName(),
                                      this.brokerConfig.getBrokerId(),
                                      this.brokerConfig.getRegisterBrokerTimeoutMills())) {
        //todo needs to send a heartbeat packet to the server (NameServer)
        doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
    }
}
private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,
                                 TopicConfigSerializeWrapper topicConfigWrapper) {
    //todo sends information to the brokerOuter API
    List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(
        this.brokerConfig.getBrokerClusterName(),
        this.getBrokerAddr(),
        this.brokerConfig.getBrokerName(),
        this.brokerConfig.getBrokerId(),
        this.getHAServerAddr(),
        topicConfigWrapper,
        this.filterServerManager.buildNewFilterServerList(),
        oneway,
        this.brokerConfig.getRegisterBrokerTimeoutMills(),
        this.brokerConfig.isCompressedRegister());
    //todo Here is the return value for messages sent like NameSever every 30s. (This is mainly used to record)
    if (registerBrokerResultList.size() > 0) {
        RegisterBrokerResult registerBrokerResult = registerBrokerResultList.get(0);
        if (registerBrokerResult != null) {
            if (this.updateMasterHAServerAddrPeriodically && registerBrokerResult.getHaServerAddr() != null) {
                this.messageStore.updateHaMasterAddress(registerBrokerResult.getHaServerAddr());
            }

            this.slaveSynchronize.setMasterAddr(registerBrokerResult.getMasterAddr());
            if (checkOrderConfig) {
                this.getTopicConfigManager().updateOrderTopicConfig(registerBrokerResult.getKvTable());
            }
        }
    }
}
public List<RegisterBrokerResult> registerBrokerAll(..., final boolean compressed) {
    final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();
    List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
    if (nameServerAddressList != null && nameServerAddressList.size() > 0) {

        //todo Heart Pack Format:
        //Header: brokerAddr, brokerld, brokerName,
        //Cluster Name, haServerAddr, compressed. Requestor:
        //Theme Configuration Information, Filter Server Configuration Information, CRC32 Checksum
        final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
        requestHeader.setBrokerAddr(brokerAddr);
        requestHeader.setBrokerId(brokerId);
        requestHeader.setBrokerName(brokerName);
        requestHeader.setClusterName(clusterName);
        requestHeader.setHaServerAddr(haServerAddr);
        requestHeader.setCompressed(compressed);

        RegisterBrokerBody requestBody = new RegisterBrokerBody();
        requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
        requestBody.setFilterServerList(filterServerList);
        final byte[] body = requestBody.encode(compressed);
        final int bodyCrc32 = UtilAll.crc32(body);
        requestHeader.setBodyCrc32(bodyCrc32);
        //todo Sends to Multiple NameServer s Send Together Using CDL
        //todo countDownLatch starter gun
        final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
        for (final String namesrvAddr : nameServerAddressList) {
            brokerOuterExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);
                        if (result != null) {
                            registerBrokerResultList.add(result);
                        }

                        log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr);
                    } catch (Exception e) {
                        log.warn("registerBroker Exception, {}", namesrvAddr, e);
                    } finally {
                        countDownLatch.countDown(); // Starter gun used
                    }
                }
            });
        }

        try {
            countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
        }
    }
    return registerBrokerResultList;
}
private RegisterBrokerResult registerBroker(...) throws RemotingCommandException, MQBrokerException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
InterruptedException {
    //The TODO is encapsulated and sent to NameServer by DefaultRequestProcessor, according to RequestCode
    RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);
    request.setBody(body);
    if (oneway) { // oneway processing
...
    RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);
    assert response != null;
    switch (response.getCode()) {
        case ResponseCode.SUCCESS: {
            RegisterBrokerResponseHeader responseHeader =
                (RegisterBrokerResponseHeader) response.decodeCommandCustomHeader(RegisterBrokerResponseHeader.class);
            RegisterBrokerResult result = new RegisterBrokerResult();
            result.setMasterAddr(responseHeader.getMasterAddr());
            result.setHaServerAddr(responseHeader.getHaServerAddr());
            if (response.getBody() != null) {
                result.setKvTable(KVTable.decode(response.getBody(), KVTable.class));
            }
            return result;
        }
        default:
            break;
    }
    throw new MQBrokerException(response.getCode(), response.getRemark(), requestHeader == null ? null : requestHeader.getBrokerAddr());
}

The request was processed by Namesrv

public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
     if (ctx != null) {
         ...
     }
     switch (request.getCode()) {
             ...
                 //Judgment is to register Broker information
                 case RequestCode.REGISTER_BROKER:
             Version brokerVersion = MQVersion.value2Version(request.getVersion());
             if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
                 return this.registerBrokerWithFilterServer(ctx, request);
             } else {
                 //Register Broker Information
                 return this.registerBroker(ctx, request);
             }
         case RequestCode.UNREGISTER_BROKER:
             return this.unregisterBroker(ctx, request);
             //todo producers get routing information from NameServer
         case RequestCode.GET_ROUTEINFO_BY_TOPIC:
             return this.getRouteInfoByTopic(ctx, request);
             ...
                 default:
             break;
     }
     return null;
 }
public RemotingCommand registerBroker(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
.. 
        //todo registers the Broker information core method, calls the registerBroker() method, and uses a read-write lock
        RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
            requestHeader.getClusterName(),
            requestHeader.getBrokerAddr(),
            requestHeader.getBrokerName(),
            requestHeader.getBrokerId(),
            requestHeader.getHaServerAddr(),
            topicConfigWrapper,
            null,
            ctx.channel()
        );

        responseHeader.setHaServerAddr(result.getHaServerAddr());
        responseHeader.setMasterAddr(result.getMasterAddr());

        byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG);
        response.setBody(jsonValue);
        response.setCode(ResponseCode.SUCCESS);
        response.setRemark(null);
        return response;
    }

Topic Routing Registration and Removal Mechanism

Routing registration and discovery (read-write locks guarantee high concurrency when sending messages)

The routing information is retrieved when the message is sent, and the Broker updates the routing information periodically, so the routing table

1. Producers need frequent acquisition when sending messages. Read the table.

2. Broker Timer (30s) updates a routing table. Write the table.

In order to improve the high concurrency (thread security) of message sending, a pair of locks, a read lock and a write lock are maintained. By separating the read and write locks, concurrency is greatly improved compared to the general exclusive locks.

Write lock

Broker reports to NameServer every 30 seconds that he is still alive (contains a lot of information) Write locks are used here. Because the data will eventually be written to NameServer's memory (saved using HashMap)

//Registration Broker Core Method NameServer mainly does service registration and discovery
    public RegisterBrokerResult registerBroker(...) {
        RegisterBrokerResult result = new RegisterBrokerResult();
        try {
            try {
                //Register routing information using write locks
                this.lock.writeLock().lockInterruptibly();
                //Maintain Cluster AddrTable
                Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
                if (null == brokerNames) {
                    brokerNames = new HashSet<String>();
                    this.clusterAddrTable.put(clusterName, brokerNames);
                }
                brokerNames.add(brokerName);

                boolean registerFirst = false;
                //Maintain brokerAddrTable
                BrokerData brokerData = this.brokerAddrTable.get(brokerName);
                //First registration creates brokerData
                if (null == brokerData) {
                    registerFirst = true;
                    brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
                    this.brokerAddrTable.put(brokerName, brokerData);
                }
                //Update Broker not first registered
                Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
                //Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>
                //The same IP:PORT must only have one record in brokerAddrTable
                Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
                while (it.hasNext()) {
...
                }

                String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
                registerFirst = registerFirst || (null == oldAddr);
                //Maintain topicQueueTable
                if (null != topicConfigWrapper
                    && MixAll.MASTER_ID == brokerId) {
...
                }
...
            } finally {
                this.lock.writeLock().unlock();
            }
        } catch (Exception e) {
            log.error("registerBroker Exception", e);
        }
        return result;
    }

Read Lock

When a producer sends a message, he or she needs to get routing information from NameServer, where a read lock is used.

//  MQClientAPIImpl.java
public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis,
                                                      boolean allowTopicNotExist)
    throws MQClientException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
    GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader();
    requestHeader.setTopic(topic);
    //todo producers get routing information from NameServer
    RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader);

    RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis);
// DefaultRequestProcessor.processRequest(): This is the code Namesrv uses to process requests
public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                      RemotingCommand request) throws RemotingCommandException {
    ...
	switch (request.getCode()) {
		...
        //todo producers get routing information from NameServer
        case RequestCode.GET_ROUTEINFO_BY_TOPIC:
        return this.getRouteInfoByTopic(ctx, request);
} // DefaultRequestProcessor.getRouteInfoByTopic()

public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
                                               RemotingCommand request) throws RemotingCommandException {
	final RemotingCommand response = RemotingCommand.createResponseCommand(null);
    final GetRouteInfoRequestHeader requestHeader =
    	(GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);
        //todo Here is used to get route information for NameServer pickupTopicRouteData()
	TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());
} // RouteInfoManager. pickupTopicRouteData()
    //Return routing table from NameServer (read lock used)
public TopicRouteData pickupTopicRouteData(final String topic) {
	TopicRouteData topicRouteData = new TopicRouteData();
    // ...
	try {
    	try {
        	//Read Lock
	        this.lock.readLock().lockInterruptibly();
    	    List<QueueData> queueDataList = this.topicQueueTable.get(topic);

Design highlights

Because Broker sends a heartbeat packet to NameServer every 30 seconds, this operation updates the status of the broker each time, but at the same time the producer sends a message that also requires the status of the broker and frequently reads it. So there's a paradox here that Broker's status is updated regularly and read more frequently. How to increase concurrency here, especially when the producer sends messages, so a read-write lock mechanism (for scenarios with more reads and less writes) is used.

For each heartbeat packet NameServer receives, it updates the status information about the broker in the brokerLiveTable and the routing table (topicQueueTable, brokerAddrTable, brokerLiveTable, filterServerTable). Updating the above routing table uses a read-write lock with less lock granularity, allowing multiple message senders (Producers) to read concurrently to ensure high concurrency when sending messages. However, NameServer only processes one Broker heartbeat at a time and multiple heartbeat packets request serial execution. This is also a classic use scenario for read-write locks. For more information on read-write locks, refer to Concurrent Programming.

Routing culling mechanism

Broker sends a heartbeat packet to NameServer every 30 seconds. The heartbeat packet contains the BrokerId, Broker address, Broker name, the name of the cluster to which the Broker belongs, and the list of filter servers associated with the Broker. But if the Broker is down, NameServer cannot receive the heartbeat packet. How does NameServer remove these invalid brokers at this time? RLiveTable status table, if the last UpdateTimestamp timestamp of BrokerLive is more than 120 seconds from the current time, the Broker is considered invalid, the Broker is removed, the connection to the Broker is closed, and topicQueueTable, brokerAddrTable, brokerLiveTable, filterServerTable are updated.

RocketMQ has two triggers to delete routing information:

  • NameServer periodically scans brokerLiveTable to detect the time difference between the last heartbeat packet and the current system. If the time exceeds 120s, the broker needs to be removed.

  • Broker executes unregisterBroker instructions when shutting down normally

Both routes delete in the same way, deleting the broker-related information from the related routing table

public boolean initialize() {
...
    //Scan every 10s as active Broker
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            //todo scans every 10s as active Broker
            NamesrvController.this.routeInfoManager.scanNotActiveBroker();
        }
    }, 5, 10, TimeUnit.SECONDS);
}

//Broker is considered invalid if it exceeds 120s
public void scanNotActiveBroker() {
    //Get brokerLiveTable
    Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
    //Traversing brokerLiveTable
    while (it.hasNext()) {
        Entry<String, BrokerLiveInfo> next = it.next();
        long last = next.getValue().getLastUpdateTimestamp();
        //If the heartbeat packet is received more than 120 seconds from that time
        if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
            //Close Connection
            RemotingUtil.closeChannel(next.getValue().getChannel());
            //Remove broker
            it.remove();
            log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
            //Maintain routing tables (update routing tables)
            this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
        }
    }
}

Storage of NameServer

NameServer stores the following information:

// RouteInfoManager.java
//Read-write lock
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// Topic Message Queuing Routing Information, Load Balancing based on Routing Table when sending messages
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable; 
// Broker base information, including brokerName, cluster name, primary and standby broker address
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
// Broker cluster information, stores all broker names in the cluster
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// Broker status information, which is replaced every time NameServer receives a heartbeat packet
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
// A list of FilterServer s on the Broker for class mode message filtering.
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;

The implementation of NameServer ** is memory based and **NameServer does not persist routing information. The task of persistence is left to Broker.

Client Start Core Process

DefaultMQProducer is the only default implementation of MQProducer, which implements the MQProducer interface by inheriting the ClientConfig class (client configuration class), which can be configured such as sendMsgTimeout timeout, producerGroup producer group maximum message capacity, and whether compression is enabled


The sender and consumer of the message in RocketMQ belong to the client

Each client is an MQClientInstance, and each ClientConfig corresponds to an instance.

So different producers and consumers share an instance of MQClientInstance if they reference the same client configuration (ClientConfig). MQClientInstance is not an external application class, that is, the user does not need to instantiate himself to use it. Furthermore, the instantiation of MQClientInstance is not used directly after new, but through the type of MQClientManager. MQClientManager is a singleton class that uses hungry-man mode design to ensure thread safety. His role is to provide an instance of MQClientInstance, which RocketMQ believes

Instances of MQClientInstance are reusable instances. As long as the client-related feature parameters are the same, an instance of MQClientInstance will be reused. We can see the source code

// Instantiate message producer Producer
DefaultMQProducer producer = new DefaultMQProducer("AsyncProducer");
// Set the address of NameServer
producer.setNamesrvAddr("127.0.0.1:9876");//localhost
// Start Producer Instance
producer.start();

public void start() throws MQClientException {
    this.setProducerGroup(withNamespace(this.producerGroup));
    //The core of todo Producer startup process is defaultMQProducerImpl.start();
    this.defaultMQProducerImpl.start();
    if (null != traceDispatcher) {
        try {
            traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
        } catch (MQClientException e) {
            log.warn("trace dispatcher start failed ", e);
        }
    }
}

//Startup process core for todo Producer
public void start(final boolean startFactory) throws MQClientException {
    switch (this.serviceState) {
            //todo must have been here for the first time
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            //Check if the producer group meets the requirements
            this.checkConfig();
            //Change current instanceName to process ID
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                this.defaultMQProducer.changeInstanceNameToPID();
            }
            //todo gets MQ client instance
            //There is only one instance of MQClientManager in the entire JVM, maintaining an MQClientInstance cache table
            //ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable = new ConcurrentHashMap<String,MQClientInstance>();
            //Only one MQClientInstance will be created for the same clientId.
            //MQClientInstance encapsulates the RocketMQ Network Processing API and is the network channel through which message producers and consumers interact with NameServer and Broker
            this.mQClientFactory = 
                MQClientManager.getInstance().
                getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook); // Core Code
            //Register current producer into MQClientInstance management to facilitate subsequent network requests
            boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
            if (!registerOK) {
                this.serviceState = ServiceState.CREATE_JUST;
                throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup()
                                            + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
                                            null);
            }
            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
            //Start Producer
            if (startFactory) {
                //todo ultimately calls MQClientInstance
                mQClientFactory.start();
            }
...
}

public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig) {
    return getOrCreateMQClientInstance(clientConfig, null);
}
    
    public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
        // Build Client ID
        String clientId = clientConfig.buildMQClientId();
        //Depending on the client ID or client instance
        MQClientInstance instance = this.factoryTable.get(clientId);
        //If the instance is empty, create a new instance and add it to the instance table
        if (null == instance) {
            instance =
                new MQClientInstance(clientConfig.cloneClientConfig(),
                                     this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
            MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance);
            if (prev != null) {
                instance = prev;
                log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId);
            } else {
                log.info("Created new MQClientInstance for clientId:[{}]", clientId);
            }
        }

        return instance;
    }

The parameters in ClientConfig are the same. These Clients will reuse an MQClientInstance. When using it, be aware of the relationship between Client and MQClientInstance in your program. Let's take a look at how RocketMQ reuses an MQClientInstance with Client for parameter configurations IP@instanceName@unitName, such a string, where I don't explain IP, InstanceName can be set, Producer and Constucer have corresponding API settings, if the default value is not set, that is, Client's PID, UnitName does not set the default to null, Of course, you can set this value by DefaultMQProducer#setUnitName(), which is inherited from the ClientConfig class.

// IP@instanceName@unitName: reuse if same
public String buildMQClientId() {
    StringBuilder sb = new StringBuilder();
    sb.append(this.getClientIP());

    sb.append("@");
    sb.append(this.getInstanceName());
    if (!UtilAll.isBlank(this.unitName)) {    // todo unitName does not set default to null
        sb.append("@");
        sb.append(this.unitName);
    }

    return sb.toString();
}

When to establish a client connection


**Note: ** Connections in MQClientInstance are created on demand, that is, when data interaction with the client is required

public static void main(String[] args) throws Exception {
...
	// Start Producer Instance
    producer.start();
    // Connect when start or send: The answer is when send
	producer.send(msg, new SendCallback() { // One more callback was sent asynchronously
    }
...
}
// DefaultMQProducer.java
public void send(Message msg,SendCallback sendCallback)  {
	msg.setTopic(withNamespace(msg.getTopic()));
    this.defaultMQProducerImpl.send(msg, sendCallback);
}
// NettyRemotingClient.java
public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
    throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
    long beginStartTime = System.currentTimeMillis();
    //todo Create a connection here
    final Channel channel = this.getAndCreateChannel(addr);
    if (channel != null && channel.isActive()) {}
}

private Channel getAndCreateChannel(final String addr) throws RemotingConnectException, InterruptedException {
    if (null == addr) {
        return getAndCreateNameserverChannel();
    }

    ChannelWrapper cw = this.channelTables.get(addr);
    if (cw != null && cw.isOK()) {
        return cw.getChannel();
    }

    return this.createChannel(addr);
}

Posted by bonekrusher on Sun, 28 Nov 2021 16:41:14 -0800