Aprendizaje introductorio de RocketMQ (4) análisis del código fuente de las noticias de producción del productor

Uno, 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);
    }
}

Dos, análisis de código fuente

1. Preparación

1.1 、 nuevo 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);
}
  • Asignar valor a producerGroup

  • nuevo DefaultMQProducerImpl ()

1.2 、 setNamesrvAddr ()

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

2. Iniciar

2.1 、 inicio ()

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

Para reiterar: la longitud no es cada línea de código que se va a analizar, no tiene sentido. El texto redundante parece molesto y me dificulta escribir, solo analiza el proceso y los principios centrales. Por tanto, se corta la basura inútil.

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. Resumen

  • Inicie la instancia MQClientAPIImpl, donde se encapsula el método de comunicación entre el cliente y el Broker.

  • Inicie varias tareas de cronometraje, latidos del corazón con Broker, etc.

  • Inicie el servicio de extracción de mensajes.

  • Inicie el servicio de equilibrio de carga.

  • Inicie el servicio Producer predeterminado (iniciado repetidamente, porque el cliente lo inició al principio).

3. Envía un mensaje

3.1 、 nuevo mensaje

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);
}
  • Reúna el cuerpo del mensaje: contenido del mensaje, etiqueta, claves, tema, 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. Preguntas de la entrevista

Entrevistador: ¿Cómo envía mensajes RocketMQ?

Encuestado: Primero, debe configurar el nombre del grupo de productores, la dirección y el tema de namesrv, y el contenido del mensaje que se enviará, luego inicie el método start () del Producer y llame al método send () para enviar después de la el inicio está completo.

El método start () verificará el namesrv, el nombre del grupo de productores y otra verificación de parámetros dentro, y luego obtendrá un objeto mQClientFactory dentro, que contiene todas las API que se comunican con el Broker, y luego iniciará el canal de respuesta de solicitud a través de mQClientFactory, principalmente netty Luego, inicie algunas tareas de cronometraje, como el latido con el intermediario, etc., e inicie el servicio de equilibrio de carga, etc., y marque el estado del servicio como EN EJECUCIÓN si el inicio es exitoso.

Una vez que se completa el inicio, se llama al método send () para enviar un mensaje. Hay tres métodos de envío, sincrónico, asincrónico y oneWay, todos los cuales son similares. La única diferencia es que se utilizan varios grupos de subprocesos asincrónicos para enviar solicitudes de forma asincrónica, mientras que síncrona es el hilo de solicitud actual directamente Los procesos centrales que se llaman sincrónicamente son:

Primero seleccione una cola adecuada para almacenar el mensaje, y después de la selección, junte un objeto de parámetro de encabezado y envíelo al corredor en forma de netty.

Vale la pena señalar aquí que si el envío falla, se reintentará automáticamente. El número predeterminado de envío síncrono es 3, es decir, se reintentará automáticamente 2 veces después del error.

Cómo elegir la cola, cómo hacer el algoritmo de equilibrio de carga, cómo conservar el almacenamiento después de que el corredor lo recibe, etc., etc. Si la longitud es demasiado larga, ¡dañará el cerebro de todos! ! !

5. Patrones de diseño

5.1, modo singleton

2.1.1、defaultMQProducerImpl.start()Esta parte ya se ha mencionado, el siguiente código

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

Echemos un vistazo más de cerca y veamos qué tipo de singleton es:

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

Hmm ... bueno, es un estilo simple y crudo de hombre hambriento.

5.2, modo de estado

2.1.1、defaultMQProducerImpl.start()Aún así, hay muchos de ellos switch..case, de hecho, este es un patrón de estado irregular. Primero mire la definición del modo de estado:

El patrón de estado permite que un objeto cambie su comportamiento cuando cambia su estado interno. El objeto parece haber cambiado de clase.

Analice el código anterior nuevamente, ¿no es solo para usar una variable miembro serviceState para registrar y administrar su propio estado de servicio? La única diferencia con el modo de estado estándar es que no usa subclases de estado, sino que lo usa switch-casepara implementar diferentes comportamientos en diferentes estados.

5.3. Modo fachada

Veamos primero la definición de modo de fachada:

La función principal del modo fachada es proporcionar al cliente una interfaz que pueda acceder al sistema y ocultar la complejidad interna del sistema.

Echemos un vistazo al Producer de RocketMQ, que es típico: proporciona una interfaz que puede acceder al sistema y oculta la complejidad del sistema .

A nivel de código, es obvio que somos DefaultMQProducerobjetos nuevos , pero las operaciones internas reales son de hecho DefaultMQProducerImplobjetos. Por ejemplo, los métodos de inicio y envío en el código fuente son ambos

/**
 * {@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. Resumen

Al absorber la experiencia de Daniel en la escritura de código, este modelo se puede utilizar en requisitos de desarrollo. ¿Cuál es el propósito de aprender el código fuente?

1: Entrevista alardeando

2: Aprenda excelentes ideas de diseño de código

6. Diagrama de tiempos

Supongo que te gusta

Origin blog.csdn.net/My_SweetXue/article/details/107381439
Recomendado
Clasificación