RocketMQ入門学習(4)プロデューサーメッセージソースコードの分析

一、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);
    }
}

2、ソースコード分析

1.準備

1.1、新しい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);
}
  • プロデューサーグループに値を割り当てる

  • 新しいDefaultMQProducerImpl()

1.2、setNamesrvAddr()

/**
 * {@link org.apache.rocketmq.client.ClientConfig}
 */
public void setNamesrvAddr(String namesrvAddr) {
    this.namesrvAddr = namesrvAddr;
}
  • namesrvに値を割り当てます

2.開始

2.1、start()

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

繰り返しになりますが、長さは解析されるコードのすべての行ではなく、意味がありません。冗長なテキストは煩わしく見え、書くのに苦労します。コアプロセスと原則を分析するだけです。そのため、無駄なゴミを切り出します。

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。まとめ

  • インスタンスMQClientAPIImplを開始します。ここで、クライアントとブローカー間の通信方法がカプセル化されます。

  • さまざまなタイミングタスク、ブローカーを使用したハートビートなどを開始します。

  • メッセージプルサービスを開始します。

  • 負荷分散サービスを開始します。

  • デフォルトのProducerサービスを開始します(クライアントが最初にこれを開始したため、繰り返し開始されます)。

3.メッセージを送信します

3.1、新しいメッセージ

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);
}
  • メッセージ本文(メッセージの内容、タグ、キー、トピックなど)をつなぎ合わせます。

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.面接の質問

インタビュアー:RocketMQはどのようにメッセージを送信しますか?

回答者:最初に、プロデューサーグループ名、namesrvアドレスとトピック、および送信するメッセージのコンテンツを構成する必要があります。次に、プロデューサーのstart()メソッドを開始し、send()メソッドを呼び出して送信します。スタートが完了しました。

start()メソッドは、namesrv、プロデューサーグループ名、およびその他のパラメーター検証を内部でチェックし、ブローカーと通信するすべてのAPIを含むmQClientFactoryオブジェクトを内部で取得し、mQClientFactory、主にnettyを介して要求応答チャネルを開始します。 、次に、ブローカーとのハートビートなどのタイミングタスクを開始し、負荷分散サービスなどを開始し、開始が成功した場合はサービスのステータスをRUNNINGとしてマークします。

開始が完了すると、send()メソッドが呼び出されてメッセージが送信されます。同期、非同期、oneWayの3つの送信メソッドがあり、すべて類似しています。唯一の違いは、送信に複数の非同期スレッドプールが使用されることです。非同期でリクエストしますが、同期は現在のリクエストスレッドです。同期的に呼び出されるコアプロセスは次のとおりです。

最初にメッセージを格納するための適切なキューを選択し、選択後、ヘッダーパラメータオブジェクトをつなぎ合わせて、nettyの形式でブローカーに送信します。

ここで、送信が失敗した場合、自動的に再試行されることに注意してください。同期送信のデフォルト数は3です。つまり、失敗後に2回自動的に再試行されます。

キューの選択方法、負荷分散アルゴリズムの実行方法、ブローカーがストレージを受信した後のストレージの永続化方法など。長すぎるとみんなの脳が痛くなります!

5.デザインパターン

5.1、シングルトンモード

2.1.1、defaultMQProducerImpl.start()この部分はすでに言及されています、次のコード

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

どのようなシングルトンかを詳しく見てみましょう。

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

うーん...まあ、それはシンプルで粗野な空腹の男のスタイルです。

5.2、状態モード

2.1.1、defaultMQProducerImpl.start()それでもこれはたくさんありますswitch..caseが、実はこれは不規則な状態パターンです。まず、状態モードの定義を見てください。

状態パターンにより、オブジェクトは内部状態が変化したときに動作を変更できます。オブジェクトはクラスを変更したように見えます。

上記のコードをもう一度分析します。メンバー変数serviceStateを使用して、独自のサービス状態を記録および管理するだけではありませんか?標準状態モードとの唯一の違いは、状態サブクラスを使用しないことですがswitch-case、さまざまな状態でさまざまな動作を実装するため使用します。

5.3。ファサードモード

まず、ファサードモードの定義を見てみましょう。

ファサードモードの主な機能は、システムにアクセスしてシステムの内部の複雑さを隠すことができるインターフェイスをクライアントに提供することです。

RocketMQのプロデューサーを見てみましょう。これは典型的なものです。システムにアクセスできるインターフェイスを提供し、システムの複雑さを隠します

コードレベルでは、私たちが新しいDefaultMQProducerオブジェクトであることは明らかですが、実際の内部操作は確かにDefaultMQProducerImplオブジェクトです。たとえば、ソースコードのstartメソッドとsendメソッドはどちらも

/**
 * {@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。まとめ

コードの記述におけるダニエルの経験を吸収して、このモデルは開発要件で使用できます。ソースコードを学ぶ目的は何ですか?

1:面接自慢

2:優れたコード設計のアイデアを学ぶ

6.タイミング図

おすすめ

転載: blog.csdn.net/My_SweetXue/article/details/107381439