RocketMQ introductory learning (4) analysis of the source code of producer production news

One, Demo

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

/**
 * Description:
 *
 * @author TongWei.Chen 2020-06-21 11:32:58
 */
public class ProducerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("my-producer");
        producer.setNamesrvAddr("124.57.180.156:9876");
        producer.start();

        Message msg = new Message("myTopic001", "hello world".getBytes());
        SendResult result = producer.send(msg);
        System.out.println("发送消息成功!result is : " + result);
    }
}

Two, source code analysis

1. Preparation

1.1、new DefaultMQProducer()

public DefaultMQProducer(final String producerGroup) {
    this(null, producerGroup, null);
}

public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
    // null
    this.namespace = namespace;
    // my-producer
    this.producerGroup = producerGroup;
    // new DefaultMQProducerImpl(this, null);
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}
  • Assign value to producerGroup

  • new DefaultMQProducerImpl()

1.2、setNamesrvAddr()

/**
 * {@link org.apache.rocketmq.client.ClientConfig}
 */
public void setNamesrvAddr(String namesrvAddr) {
    this.namesrvAddr = namesrvAddr;
}
  • Assign a value to namesrv

2. Start

2.1、start()

@Override
public void start() throws MQClientException {
    this.defaultMQProducerImpl.start();
}

To reiterate: the length is not every line of code to be parsed, it is meaningless. Redundant text looks annoying and troubles me writing, only analyzing the core process and principles. Therefore, useless garbage is cut out.

2.1.1、defaultMQProducerImpl.start()

private ServiceState serviceState = ServiceState.CREATE_JUST;

public void start(final boolean startFactory) throws MQClientException {
    switch (this.serviceState) {
        // 默认为CREATE_JUST状态
        case CREATE_JUST:
            //  先默认成启动失败,等最后完全启动成功的时候再置为ServiceState.RUNNING
            this.serviceState = ServiceState.START_FAILED;
   /**
    * 检查配置,比如group有没有写,是不是默认的那个名字,长度是不是超出限制了,等等一系列验证。
    */
            this.checkConfig();
   /*
             * 单例模式,获取MQClientInstance对象,客户端实例。也就是Producer所部署的机器实例对象,负责操作的主要对象。
             */
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
   /**
    * 注册producer,其实就是往producerTable map里仍key-value
    * private final ConcurrentMap<String, MQProducerInner> producerTable = 
    * new ConcurrentHashMap<String, MQProducerInner>();
    * producerTable.putIfAbsent("my-producer", DefaultMQProducerImpl);
    */
            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);
            }
   // 将topic信息存到topicPublishInfoTable这个map里
            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
   
            if (startFactory) {
                // 真正的启动核心类
                mQClientFactory.start();
            }
   // 都启动完成,没报错的话,就将状态改为运行中
            this.serviceState = ServiceState.RUNNING;
            break;
        case RUNNING:
        case START_FAILED:
        case SHUTDOWN_ALREADY:
            throw new MQClientException("The producer service state not OK, maybe started once, "
                                        + this.serviceState
                                        + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
                                        null);
        default:
            break;
    }

    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();

    this.timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            try {
                // 每隔1s扫描过期的请求
                RequestFutureTable.scanExpiredRequest();
            } catch (Throwable e) {
                log.error("scan RequestFutureTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
}

2.1.2、mQClientFactory.start()

public void start() throws MQClientException {
    synchronized (this) {
        // 默认为CREATE_JUST状态
        switch (this.serviceState) {
            case CREATE_JUST:
                // 先默认成启动失败,等最后完全启动成功的时候再置为ServiceState.RUNNING
                this.serviceState = ServiceState.START_FAILED;
                
                // 启动请求响应通道,核心netty
                this.mQClientAPIImpl.start();
                /**
                 * 启动各种定时任务
                 * 1.每隔2分钟去检测namesrv的变化
                 * 2.每隔30s从nameserver获取topic的路由信息有没有发生变化,或者说有没有新的topic路由信息
                 * 3.每隔30s清除下线的broker
                 * 4.每隔5s持久化所有的消费进度
                 * 5.每隔1分钟检测线程池大小是否需要调整
                 */
                this.startScheduledTask();
                // 启动拉取消息服务
                this.pullMessageService.start();
                // 启动Rebalance负载均衡服务
                this.rebalanceService.start();
                /**
                 * 这里再次调用了DefaultMQProducerImpl().start()方法,这TM不死循环了吗?
                 * 不会的,因为他传递了false,false再DefaultMQProducerImpl().start()方法里不会再次调用mQClientFactory.start();
                 * 但是这也重复执行了两次DefaultMQProducerImpl().start()方法里的其他逻辑,不知道为啥这么搞,没看懂。
                 */
                this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
                // 都启动完成,没报错的话,就将状态改为运行中
                this.serviceState = ServiceState.RUNNING;
                break;
            case START_FAILED:
                throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
            default:
                break;
        }
    }
}

2.2. Summary

  • Start the instance MQClientAPIImpl, where the method of communication between the client and the Broker is encapsulated.

  • Start various timing tasks, heartbeats with Broker, etc.

  • Start the message pull service.

  • Start the load balancing service.

  • Start the default Producer service (started repeatedly, because the client started this at the beginning).

3. Send a message

3.1、new Message

public Message(String topic, byte[] body) {
    this(topic, "", "", 0, body, true);
}

public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) {
    this.topic = topic;
    this.flag = flag;
    this.body = body;
    if (tags != null && tags.length() > 0)
        this.setTags(tags);
    if (keys != null && keys.length() > 0)
        this.setKeys(keys);
    this.setWaitStoreMsgOK(waitStoreMsgOK);
}
  • Piece together the message body: message content, tag, keys, topic, etc.

3.2、producer.send(msg)

public SendResult send(
    Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // 参数校验
    Validators.checkMessage(msg, this);
    // 设置topic
    msg.setTopic(withNamespace(msg.getTopic()));
    // 发送消息
    return this.defaultMQProducerImpl.send(msg);
}

3.2.1、sendDefaultImpl

private SendResult sendDefaultImpl(
    Message msg,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // 检查Producer上是否是RUNNING状态
    this.makeSureStateOK();
    // 消息格式的校验
    Validators.checkMessage(msg, this.defaultMQProducer);
    // 尝试获取topic的路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        // 选择消息要发送的队列
        MessageQueue mq = null;
        // 发送结果
        SendResult sendResult = null;
        // 自动重试次数,this.defaultMQProducer.getRetryTimesWhenSendFailed()默认为2,如果是同步发送,默认重试3,否则重试1次
        int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
        int times = 0;
        for (; times < timesTotal; times++) {
            // 选择topic的一个queue,然后往这个queue里发消息。
            MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
            if (mqSelected != null) {
                mq = mqSelected;
                try {
                    // 真正的发消息方法
                    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                    switch (communicationMode) {
                        case ASYNC:
                            return null;
                        case ONEWAY:
                            return null;
      // 同步的,将返回的结果返回,如果返回结果状态不是成功的,则continue,进入下一次循环进行重试。    
                        case SYNC:
                            if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                                if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                    continue;
                                }
                            }
                            return sendResult;
                        default:
                            break;
                    }
                } catch (RemotingException e) {
                    continue;
                } catch (MQClientException e) {
                    continue;
                } catch (...) {...}
            } else {
                break;
            }
        }

        if (sendResult != null) {
            return sendResult;
        }
    }
}

4. Interview questions

Interviewer: How does RocketMQ send messages?

Respondent: First, you need to configure the producer group name, namesrv address and topic, and the content of the message to be sent, then start the start() method of the Producer, and call the send() method to send after the start is complete.

The start() method will check the namesrv, producer group name and other parameter verification inside, and then get an mQClientFactory object inside, which contains all the APIs that communicate with the Broker, and then start the request response channel through mQClientFactory, mainly netty , Then start some timing tasks, such as the heartbeat with the broker, etc., and start the load balancing service, etc., and mark the status of the service as RUNNING if the start is successful.

After the start is completed, the send() method is called to send a message. There are three sending methods, synchronous, asynchronous, and oneWay, all of which are similar. The only difference is that multiple asynchronous thread pools are used to send requests asynchronously, while synchronous is the current request thread directly The core processes that are called synchronously are:

First select a suitable queue to store the message, and after the selection, piece together a header parameter object and send it to the broker in the form of netty.

It is worth noting here that if the sending fails, it will automatically retry. The default number of synchronous sending is 3, that is, it will automatically retry 2 times after failure.

How to choose the queue, how to do the load balancing algorithm, how to persist the storage after the broker receives it, and so on, and so on. If the length is too long, it will hurt everyone's brains! ! !

5. Design patterns

5.1, singleton mode

2.1.1、defaultMQProducerImpl.start()This part has already been mentioned, the following code

/*
 * 单例模式,获取MQClientInstance对象,客户端实例。也就是Producer所部署的机器实例对象,负责操作的主要对象。
 */
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);

Let's take a closer look at what kind of singleton is:

public class MQClientManager {
    // 直接new
    private static MQClientManager instance = new MQClientManager();
    // 私有构造器
    private MQClientManager() {
    }
    // getInstance
    public static MQClientManager getInstance() {
        return instance;
    }
}

Hmm... well, it's a simple and crude hungry man style.

5.2, state mode

2.1.1、defaultMQProducerImpl.start()Still this, there are a lot of them switch..case, in fact, this is an irregular state pattern. First look at the definition of the state mode:

The state pattern allows an object to change its behavior when its internal state changes. The object looks like it has changed its class.

Analyze the above code again, isn't it just to use a member variable serviceState to record and manage its own service state? The only difference from the standard state mode is that it does not use state subclasses, but uses it switch-caseto implement different behaviors in different states.

5.3. Facade mode

Let's first look at the definition of facade mode:

The main function of the facade mode is to provide the client with an interface that can access the system and hide the internal complexity of the system.

Let's look at RocketMQ's Producer, which is typical: it provides an interface that can access the system and hides the complexity of the system .

At the code level, it is obvious that we are new DefaultMQProducerobjects, but the actual internal operations are indeed DefaultMQProducerImplobjects. For example, the start and send methods in the source code are both

/**
 * {@link org.apache.rocketmq.client.producer.DefaultMQProducer}
 */
public void start() throws MQClientException {
    // DefaultMQProducerImpl的start
    this.defaultMQProducerImpl.start();
}

/**
 * {@link org.apache.rocketmq.client.producer.DefaultMQProducer}
 */
public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // DefaultMQProducerImpl的send
    return this.defaultMQProducerImpl.send(msg);
}

5.4. Summary

Absorbing Daniel's experience in writing code, this model can be used in development requirements. What is the purpose of learning source code?

1: Interview bragging

2: Learn excellent code design ideas

6. Timing diagram

Guess you like

Origin blog.csdn.net/My_SweetXue/article/details/107381439