Análisis de entrega de mensajes de RocketMQ: diagrama de secuencia, cadena de llamadas, análisis a nivel de fuente

¡Acostúmbrate a escribir juntos! Este es el octavo día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .


modelo de entrega de mensajes

En los artículos anteriores, también dibujé el diagrama del modelo de entrega de mensajes, aquí hay una breve revisión:inserte la descripción de la imagen aquí

  • El clúster productor de mensajes obtiene información de enrutamiento (equilibrio de carga) del registro y luego envía el mensaje al Brokerclúster.

  • El registro es un clúster sin estado, es decir, cada servidor no afecta a otros servidores. BrokerLa información de registro se enviará a todos los servidores del centro de registro al mismo tiempo.

  • El registro almacena información como Topic, Queue, dirección IP, etc. En circunstancias normales, cada máquina debería almacenar la misma información.

  • Broker utiliza una arquitectura maestro-esclavo para proporcionar servicios, el servidor maestro es responsable de las operaciones de escritura y el servidor esclavo es responsable de procesar las solicitudes de lectura.



proceso de entrega de mensajes

El diagrama de secuencia del envío de mensajes se muestra en la siguiente figura: inserte la descripción de la imagen aquí ProducerPrimero, debe saber a quién Brokerenviar el mensaje, por lo que el proceso específico es el siguiente:

  1. ProducerPrimero intente obtener información de enrutamiento del local
  2. Cuando no hay información de enrutamiento en caché local, la información de enrutamiento se obtiene del registro y se almacena en caché localmente.
  3. La información de enrutamiento obtenida contiene Topictodo lo siguiente Queue, y Producerse puede adoptar la estrategia de equilibrio de carga para enviar el mensaje a una determinada cola
  4. ProducerDespués de que el mensaje Brokerse envíe con éxito, el servidor devolverá el objeto del mensaje enviado con éxitoSendResult



cadena de métodos de entrega de mensajes

A continuación, se muestra la cadena general de llamadas al método desde la obtención de la tabla de enrutamiento hasta el proceso de entrega del mensaje en forma de diagrama de secuencia:

Por favor agregue la descripción de la imagenLas API principales involucradas en la figura anterior son las siguientes:

// 发送消息
DefaultMQProducer#send(Message msg);
// 发送消息,增加超时时间
DefaultMQProducer#send(Message msg, long timeout);
// 发送消息,增加发送消息的模式(异步/同步)
DefaultMQProducer#sendDefaultImpl(Message msg, CommunicationMode mode, long timeout);
复制代码
// 查询消息发送的路由信息
DefaultMQProducerImpl#tryToFindTopicPublishInfo(String topic);
复制代码
// 根据topic的名称更新注册中心的路由信息
MQClientInstance#updateTopicRouteInfoFromNameServer(String topic);
// 根据topic的名称更新注册中心的路由信息,并获取路由信息
MQClientInstance#updateTopicRouteInfoFromNameServer(String topic, Boolean isDefault, MQDefaultProducer mqDefaultProducer);
复制代码
// 根据负载均衡算法,选择一个队列进行消息发送
DefaultMQProducerImpl#selectOneMessageQueue(TopicPublishInfo topic, String lastBrokerName);
复制代码
// 发送消息
DefaultMQProducerImpl#sendKernelImpl(Message msg, MessageQueue queue);
复制代码

A continuación, realizamos un análisis a nivel de fuente, que se puede aprender de la figura anterior:

EnviarResultado

Si el mensaje se envía con éxito, SendResultse devuelve un objeto:

/**
* 发送消息结果
*/
public class SendResult {
	/**
	* 发送消息结果状态
	*/
    private SendStatus sendStatus;
    /**
	* 消息的唯一key,由Client发送消息时生成
	*/
    private String msgId;
    /**
	* 消息队列
	*/
    private MessageQueue messageQueue;
    /**
	* 消息队列偏移量
	*/
    private long queueOffset;
    /**
	* 事务ID
	*/
    private String transactionId;
    /**
	* 下一条消息的偏移量
	*/
    private String offsetMsgId;
    /**
	* 区域ID
	*/
    private String regionId;
}
复制代码

donde SendStatuses un valor de enumeración:

public enum SendStatus {
    SEND_OK,
    FLUSH_DISK_TIMEOUT,
    FLUSH_SLAVE_TIMEOUT,
    SLAVE_NOT_AVAILABLE,
}
复制代码
  • SEND_OK: el mensaje se envió correctamente y la sincronización de almacenamiento se realizó correctamente
  • FLUSH_DISK_TIMEOUT: mensaje enviado correctamente pero no se pudo almacenar
  • FLUSH_SLAVE_TIMEOUT:消息发送成功但slave节点超时
  • SLAVE_NOT_AVAILABLE:消息发送成功但slave节点不可用



消息投递源码解析

Producer发送消息

DefaultMQProducer发送消息类模型: inserte la descripción de la imagen aquí

  • MQAdmin:MQ管理的基类
  • ClientConfig:客户端配置类
  • DefaultMQProducer:消息生产者

使用Producer发送消息,具体编码实现方式如下:

  1. 创建DefaultMQProducer,传入指定发送消息所在组
  2. 设置注册中心地址,Producer会从里面获取到Topic以及队列
  3. 发送消息

发送消息时必须指定Topic,消息标签,消息体

package com.wjw;

import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;
import com.alibaba.rocketmq.remoting.common.RemotingHelper;

public class MQProducerA {
    public static void main(String[] args) throws Exception {
        // 创建消息生产者,指定组
        DefaultMQProducer producer = new DefaultMQProducer("group-A");
        // 设置注册中心地址
        producer.setNamesrvAddr("localhost");

        producer.start();

        for (int i = 0; i < 10; i++) {
            // 创建消息对象
            Message message = new Message("topic-A", "tagA", ("Hello MQ " + i)
                    .getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 设置消息延时级别
            message.setDelayTimeLevel(6);
            // 发送消息
            SendResult result = producer.send(message);
            System.out.println("发送消息结果:" + result);
        }
        producer.shutdown();
    }
}
复制代码

DefaultMQProducer

发送消息的producer.send()方法调用的是DefaultMQPrducer里的send方法:

inserte la descripción de la imagen aquí inserte la descripción de la imagen aquí 这里又调用了defaultMQProducerImpl.send(msg)

public class DefaultMQProducer extends ClientConfig implements MQProducer {

	// ...
	
	@Override
    public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        return this.defaultMQProducerImpl.send(msg);
    }
    
	// ...
	
}
复制代码

defaultMQProducerImpl

使用defaultMQProducerImplsend方法发送消息,这里的调用多传了一个超时时间参数,当producer没有指定时,取默认值3000ms:

public class DefaultMQProducerImpl implements MQProducerInner {

	// ...

	public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
		// 发送消息,指定消息发送的超时时间
        return send(msg, this.defaultMQProducer.getSendMsgTimeout());
    }

	public SendResult send(Message msg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
		// 发送消息,指定消息发送类型:同步 or 异步,超时时间
        return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
    }

	// ...

}
复制代码

inserte la descripción de la imagen aquí

sendDefaultImpl

上面调用的sendDefaultImpl方法需要做下面几件事:

  1. 获取消息路由信息,包含Topic下的队列和IP信息
  2. 选择要发送到的消息队列,这个过程会采用负载均衡策略选择一个队列进行消息存储
  3. 发送消息(sendKernelImpl)并返回结果

核心逻辑我已经标注在下面的代码片段里,非核心代码已省略

public class DefaultMQProducerImpl implements MQProducerInner {

	// ...

	/**
     * 发送消息
     *
     * @param msg                   消息
     * @param communicationMode     通信模式
     * @param sendCallback          发送回调
     * @param timeout               请求超时时间
     * @return
     * @throws MQClientException
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    private SendResult sendDefaultImpl(//
                                       Message msg, //
                                       final CommunicationMode communicationMode, //
                                       final SendCallback sendCallback, //
                                       final long timeout//
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        // 确保MQ服务正在运行
        this.makeSureStateOK();
        // 检查消息、Topic、消息体是否为空且满足系统要求
        Validators.checkMessage(msg, this.defaultMQProducer);

        // ...

        // 获取Broker的路由信息
        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
        if (topicPublishInfo != null && topicPublishInfo.ok()) {
            MessageQueue mq = null;
            Exception exception = null;
            SendResult sendResult = null;
            // 根据消息是否是同步的,确定总的发送时间
            int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
            int times = 0;
            String[] brokersSent = new String[timesTotal];
            // 循环调用发送消息方法,直到成功或超时
            for (; times < timesTotal; times++) {
                String lastBrokerName = null == mq ? null : mq.getBrokerName();
                // 选择消息要发送到的队列,默认策略是轮流发送,当发送失败时,按顺序发送到下一个Broker的MessageQueue
                MessageQueue tmpmq = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
                if (tmpmq != null) {
                    mq = tmpmq;
                    brokersSent[times] = mq.getBrokerName();
                    try {
                        beginTimestampPrev = System.currentTimeMillis();
                        // 发送消息核心方法
                        sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);
                        endTimestamp = System.currentTimeMillis();
                        // 更新Broker的可用性信息,当发送时间超时时会有30s的不可用时长。只有开启了延迟容错机制才生效
                        this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                        switch (communicationMode) {
                            case ASYNC:
                                return null;
                            case ONEWAY:
                                return null;
                            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 (MQBrokerException e) {
                        /** (省略)异常处理逻辑 **/
                    } catch (InterruptedException e) {
                        /** (省略)异常处理逻辑 **/
                    }
                } else {
                    break;
                }
            } // end of for

            // 返回发送结果
            if (sendResult != null) {
                return sendResult;
            }

            /** (省略)异常处理逻辑 **/
        }

        // 获取不到注册中心的地址信息则抛出异常
        List<String> nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList();
        if (null == nsList || nsList.isEmpty()) {
            throw new MQClientException(
                    "No name server address, please set it." + FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL), null).setResponseCode(ClientErrorCode.NoNameServerException);
        }

        // Topic为空则抛出异常
        throw new MQClientException("No route info of this topic, " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
                null).setResponseCode(ClientErrorCode.NotFoundTopicException);
    }

	// ...

}
复制代码

sendKernelImpl

其实看函数名就能看出来,这是发送消息的核心方法。

  1. 根据brokername从本地缓存表brokerAddrTable中获取Broker服务器的IP地址,如果无法从本地获取到Broker的地址,则去请求注册中心获取;
  2. El corredor abrirá dos puertos para servicios externos. Si se abre el canal VIP, el número de puerto VIP es el número de puerto original - 2
  3. Construya RequestHeaderel encabezado de la solicitud
  4. Envíe un mensaje de acuerdo con la estrategia de sincronización, ONEWAYindicando que un mensaje unidireccional no necesita devolver un resultado, y se lanzará una excepción si el envío falla

He marcado la lógica central en el fragmento de código a continuación y se ha omitido el código no central

public class DefaultMQProducerImpl implements MQProducerInner {

	// ...

	/**
     * 发送消息核心方法,返回发送结果
     *
     * @param msg               消息
     * @param mq                消息队列
     * @param communicationMode 通信模式
     * @param sendCallback      发送回调
     * @param topicPublishInfo  Topic信息
     * @param timeout           超时时间
     * @return
     * @throws MQClientException
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    private SendResult sendKernelImpl(final Message msg, //
                                      final MessageQueue mq, //
                                      final CommunicationMode communicationMode, //
                                      final SendCallback sendCallback, //
                                      final TopicPublishInfo topicPublishInfo, //
                                      final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        // 根据broker name查询Broker的地址
        String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
        // 本地缓存为空则从注册中心查询Broker的地址
        if (null == brokerAddr) {
            tryToFindTopicPublishInfo(mq.getTopic());
            brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
        }

        SendMessageContext context = null;
        if (brokerAddr != null) {
            // 是否使用VIP channel
            brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);

            byte[] prevBody = msg.getBody();
            try {

                // ......省略部分逻辑

                // 构造请求头
                SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
                // 设置producer的group
                requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
                // 设置topic名称
                requestHeader.setTopic(msg.getTopic());
                // 设置默认topic
                requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
                // 设置topic queue的数量
                requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
                // 设置queue的id
                requestHeader.setQueueId(mq.getQueueId());
                // 设置系统标记
                requestHeader.setSysFlag(sysFlag);
                // 设置消息创建时间
                requestHeader.setBornTimestamp(System.currentTimeMillis());
                requestHeader.setFlag(msg.getFlag());
                // 设置消息属性
                requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
                // 设置被消费过的次数
                requestHeader.setReconsumeTimes(0);
                requestHeader.setUnitMode(this.isUnitMode());
                // 如果topic是"%RETRY%"表示消息重发
                if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
                    if (reconsumeTimes != null) {
                        requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));
                        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);
                    }

                    String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg);
                    if (maxReconsumeTimes != null) {
                        requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));
                        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);
                    }
                }

                SendResult sendResult = null;
                // 根据消息发送的不同模式发送消息
                switch (communicationMode) {
                    case ASYNC:
                        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(//
                                brokerAddr, // 1
                                mq.getBrokerName(), // 2
                                msg, // 3
                                requestHeader, // 4
                                timeout, // 5
                                communicationMode, // 6
                                sendCallback, // 7
                                topicPublishInfo, // 8
                                this.mQClientFactory, // 9
                                this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(), // 10
                                context, //
                                this);
                        break;
                    case ONEWAY:
                    case SYNC:
                        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(//
                                brokerAddr, // 1
                                mq.getBrokerName(), // 2
                                msg, // 3
                                requestHeader, // 4
                                timeout, // 5
                                communicationMode, // 6
                                context,//
                                this);
                        break;
                    default:
                        assert false;
                        break;
                }

                if (this.hasSendMessageHook()) {
                    context.setSendResult(sendResult);
                    this.executeSendMessageHookAfter(context);
                }

                return sendResult;
            } catch (RemotingException e) {
                /** 异常处理逻辑 **/
            } catch (MQBrokerException e) {
                /** 异常处理逻辑 **/
            } catch (InterruptedException e) {
                /** 异常处理逻辑 **/
            } finally {
                msg.setBody(prevBody);
            }
        }
        // broker不存在,抛异常
        throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
    }

	// ...

}
复制代码

Se llama a este método para sendMessagerealizar la lógica de envío real:inserte la descripción de la imagen aquí


enviar mensaje

  1. Cree el objeto de solicitud para el envío de mensajes.sendMessageRequestHeader
  2. Use RemotingCommandla directiva de solicitud de creación y establezca parámetros
  3. Iniciar una solicitud de llamada remota para enviar un mensaje
  • Cuando el modo de envío de mensajes ONEWAYes , el mensaje solo se enviará una vez en una dirección
  • Cuando el modo de envío de mensajes ASYNCes , si el envío del mensaje falla, el mensaje se reenviará según el número de reintentos.
  • Cuando el modo de envío de mensajes SYNCes , envíe el mensaje directamente sin volver a intentarlo
/**
     * 发送消息,返回发送结果
     *
     * @param addr                      Broker地址
     * @param brokerName
     * @param msg                       消息
     * @param requestHeader             请求头
     * @param timeoutMillis             超时时间
     * @param communicationMode         通信模式
     * @param sendCallback              发送回调
     * @param topicPublishInfo          Topic信息
     * @param instance                  Client实例
     * @param retryTimesWhenSendFailed  最大重试次数
     * @param context                   发送消息context
     * @param producer
     * @return
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    public SendResult sendMessage(//
                         final String addr, // 1
                         final String brokerName, // 2
                         final Message msg, // 3
                         final SendMessageRequestHeader requestHeader, // 4
                         final long timeoutMillis, // 5
                         final CommunicationMode communicationMode, // 6
                         final SendCallback sendCallback, // 7
                         final TopicPublishInfo topicPublishInfo, // 8
                         final MQClientInstance instance, // 9
                         final int retryTimesWhenSendFailed, // 10
                         final SendMessageContext context, // 11
                         final DefaultMQProducerImpl producer // 12
    ) throws RemotingException, MQBrokerException, InterruptedException {
        // 创建请求,如果将sendSmartMsg设为true,可以将请求keey压缩,加快序列化
        RemotingCommand request = null;
        if (sendSmartMsg) {
            SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
        } else {
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
        }

        request.setBody(msg.getBody());

        switch (communicationMode) {
            case ONEWAY:
                // 基于Netty快速通信框架,发送消息给broker
                this.remotingClient.invokeOneway(addr, request, timeoutMillis);
                return null;
            case ASYNC:
                final AtomicInteger times = new AtomicInteger();
                this.sendMessageAsync(addr, brokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance,
                        retryTimesWhenSendFailed, times, context, producer);
                return null;
            case SYNC:
                return this.sendMessageSync(addr, brokerName, msg, timeoutMillis, request);
            default:
                assert false;
                break;
        }

        return null;
    }
复制代码

Aquí, se llama al servicio remotingClientde llamadas remotas del cliente Brokerpara enviar un mensajeinserte la descripción de la imagen aquí

Supongo que te gusta

Origin juejin.im/post/7084048029271441444
Recomendado
Clasificación