RocketMQ源码解析之消息发送(二)

目录

1:写在前面

2:RocketMQ消息数据结构

3:生产者启动流程

3.1:DefaultMQProducer

3.2:启动生产者具体的实现类 DefaultMQProducerImpl 

3.3:同步消息发送基本流程

4:总结


目录

1:写在前面

RockerMQ发送一般的消息(还有一种是事务消息,后续的博客会展开)有三种实现方式:可靠的同步传输,可靠的异步传输,单向传输,这些传输方式的前提都是基于消息体(消息数据结构),发送消息必然要有一个客户端(涉及到客户端的启动流程,是怎么初始化的),还有消息发送的流程,从哪里开始发送,发送到哪里,这都是我们需要考虑到的问题,上述只是基于单条消息的发送,要是涉及到多条消息的发送,是循环调用消息发送接口呢,还是在内部做了什么优化?以下将会具体分析。

2:RocketMQ消息数据结构


    org.apache.rocketmq.common.message.Message    

    //主题
    private String topic;
    //目前不做处理的属性
    private int flag;
    //扩展属性, tag , delayTimeLevel 消息延迟级别,用于定时消息或消息重试
    private Map properties;
    //消息体的字节数组
    private byte[] body;
    //事务,在后续的事务消息中有使用到,主要用于事务消息的状态会查
    private String transactionId;

3:生产者启动流程

3.1:DefaultMQProducer

作为MQ的默认的消息发送者客户端,实现了MQAdmin接口,提供了以下重要的接口

//创建topic
org.apache.rocketmq.client.producer.DefaultMQProducer#createTopic(java.lang.String, java.lang.String, int, int)

//消息发送with超时 ---  到broker的返回时间
org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message, long)

//带回调+超时
org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.client.producer.SendCallback, long)

//单向的
org.apache.rocketmq.client.producer.DefaultMQProducer#sendOneway(org.apache.rocketmq.common.message.Message)

//指定消息队列MessageQueueSelector ----  顺序消息的实现
org.apache.rocketmq.client.producer.DefaultMQProducer#sendOneway(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.client.producer.MessageQueueSelector, java.lang.Object)

//事务消息
org.apache.rocketmq.client.producer.DefaultMQProducer#sendMessageInTransaction(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.client.producer.LocalTransactionExecuter, java.lang.Object)

//批量发送消息
org.apache.rocketmq.client.producer.DefaultMQProducer#send(java.util.Collection, org.apache.rocketmq.common.message.MessageQueue, long)

一些核心的属性

    //生产者所属组,主要用于事务回查
    private String producerGroup;

    //默认的topicKey
    private String createTopicKey = MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC;

    //默认的主题队列大小
    private volatile int defaultTopicQueueNums = 4;

    //发送消息默认的超时时间  3s
    private int sendMsgTimeout = 3000;

    //当消息体的大小达到多少时启动压缩机制  默认4k   
    private int compressMsgBodyOverHowmuch = 1024 * 4;

    //消息发送失败的时候需要重试的次数,默认2 加上一开始的一次总共三次
    private int retryTimesWhenSendFailed = 2;

    //异步消息发送失败重试次数,和以上保持一致
    private int retryTimesWhenSendAsyncFailed = 2;

    //消息发送失败是否选择另外的broker,不存储就返回。
    private boolean retryAnotherBrokerWhenNotStoreOK = false;

    //允许发送消息的最大长度,默认4M,基本山不用动,要是一个消息体有4M,系统就需要优化了
    private int maxMessageSize = 1024 * 1024 * 4; // 4M

3.2:启动生产者具体的实现类 DefaultMQProducerImpl 

DefaultMQProducerImpl 是DefaultMQProducer里面的一个组合对象

按照以往的风格,一开始是调用start方法

public void start(final boolean startFactory) throws MQClientException {
        switch (this.serviceState) {
            //获取生产者的状态
            case CREATE_JUST:
                this.serviceState = ServiceState.START_FAILED;
                //检查配置项
                this.checkConfig();
                //如果不是默认的生产者组,就要把当前实例的名称换成为当前进程的id
                if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                    this.defaultMQProducer.changeInstanceNameToPID();
                }

                //创建MQClientInstance 用于消息的发送
                this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook);


                //向客户端工厂注册自己生产者,便于后续调用网络请求,或者心跳检测等等,如果注册失败的话,将会抛出异常
                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注册表,便于找到对应的主题
                this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());

                if (startFactory) {
                    mQClientFactory.start();
                }

                log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
                    this.defaultMQProducer.isSendMessageWithVIPChannel());
                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();
    }

3.3:同步消息发送基本流程

1:org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message)

2:org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send(org.apache.rocketmq.common.message.Message)

3:org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl

 public SendResult send(Message msg,
        long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
//设置消息模式为同步模式,超时时间使用默认的 3s
        return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
    }

4:校验消息长度

public static void checkMessage(Message msg, DefaultMQProducer defaultMQProducer)
        throws MQClientException {
        if (null == msg) {
            throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message is null");
        }
        // topic
        Validators.checkTopic(msg.getTopic());

        // body
        if (null == msg.getBody()) {
            throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body is null");
        }

        if (0 == msg.getBody().length) {
            throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body length is zero");
        }

        if (msg.getBody().length > defaultMQProducer.getMaxMessageSize()) {
            throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL,
                "the message body size over max value, MAX: " + 

// 不能超过4M
defaultMQProducer.getMaxMessageSize());
        }
    }

5:在发送消息的时候,消息是发送到broker上面的,在此之前,必须要找到对应的主题,俗称主题路由

//查找顺序 
//    topic -----》 topicPublishInfoTable(缓存) ----- 》 InfoNameServer
// 返回的是一个 根据主题查找的 消息队列  TopicPublishInfo  包含消息队列,路由信息,broker
 private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
        //根据主题在缓存中查找,如果没有找到,那么主题注册表就创建一个新的,然后更新注册表
        TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
        if (null == topicPublishInfo || !topicPublishInfo.ok()) {
            this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
            //然后更新注册表
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
            //重新获取,原因是因为其他的生产者可能也是同样的主题,这样子就可以获取最新的主题注册表了
            topicPublishInfo = this.topicPublishInfoTable.get(topic);
        }

        //如果注册表中当前主题的对应的路由信息,那么返回
        if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
            return topicPublishInfo;
        } else {
            //更新nameServer中注册表
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
            //获取最新的
            topicPublishInfo = this.topicPublishInfoTable.get(topic);
            return topicPublishInfo;
        }
    }


    private boolean orderTopic = false;
    private boolean haveTopicRouterInfo = false;

    //返回的消息队列
    private List messageQueueList = new ArrayList();

    private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();

    //路由信息
    private TopicRouteData topicRouteData;

 

3.4:选择消息队列

在3.3中已经获取到了消息发送者需要的消息队列的相关信息,因为在MQ中默认会有四个消息队列,那么RocketMQ是怎么选择某一个消息队列的呢?

 public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
        //缓存机制,先查找上一次使用发送失败的broker,如果失败,说明需要重新选择一个broker
        if (lastBrokerName == null) {
            return selectOneMessageQueue();
        } else {
            //取模获得消息队列的下标
            int index = this.sendWhichQueue.getAndIncrement();
            for (int i = 0; i < this.messageQueueList.size(); i++) {
                int pos = Math.abs(index++) % this.messageQueueList.size();
                if (pos < 0)
                    pos = 0;
                //从消息列表list中拿一个队列出来进行消息信道的存放
                MessageQueue mq = this.messageQueueList.get(pos);
                if (!mq.getBrokerName().equals(lastBrokerName)) {
                    return mq;
                }
            }
            return selectOneMessageQueue();
        }
    }

为什么上次出故障的broker还会出现在列表中呢?这是因为 连接nameserver的时候,会存在这扫描的延迟,也就是说,在broker宕机的时间内,nameserver还没有感知到,nameserver的心跳检测频率为 10s/次

 

3.5:使用消息队列发送消息

在3.4中已经获取到了即将要放松的消息队列(和broker绑定的),接下来一起看看是怎么发送消息的。

org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendKernelImpl

 

 //用于判定消息的发送是否超时
        long beginStartTime = System.currentTimeMillis();
        //根据brokername获取到broker地址
        String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
        if (null == brokerAddr) {
            //如果没有找到的话,就需要重新根据topic查找路由信息,里面涉及到更新
            tryToFindTopicPublishInfo(mq.getTopic());
            brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
        }

        SendMessageContext context = null;
        if (brokerAddr != null) {
            brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);

            //获取到消息的消息体 字节数组
            byte[] prevBody = msg.getBody();
            try {
                //for MessageBatch,ID has been set in the generating process
                //看下是否是批量消息  MessageBatch extends Message 
                if (!(msg instanceof MessageBatch)) {
                    MessageClientIDSetter.setUniqID(msg);
                }

                boolean topicWithNamespace = false;
                if (null != this.mQClientFactory.getClientConfig().getNamespace()) {
                    msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace());
                    topicWithNamespace = true;
                }

                int sysFlag = 0;
                boolean msgBodyCompressed = false;
                //看下消息是否要压缩,4k
                if (this.tryToCompressMessage(msg)) {
                    sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
                    msgBodyCompressed = true;
                }
                //是否设置了事务消息
                final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
                    //如果是事务消息的话,会给消息一个 PRE 的标志,后续会添加一个事务消息的专用处理器
                    sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
                }

接下来要做的就是封装消息体,形成约定的消息格式进行传输,创建请求头

  //创建消息请求头
                SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
                //设置生产者组
                requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
                //设置消息主题
                requestHeader.setTopic(msg.getTopic());
                requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
                //默认的消息队列的数量  默认4个
                requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
                //设置消息队列id
                requestHeader.setQueueId(mq.getQueueId());
                //消息的类型
                requestHeader.setSysFlag(sysFlag);
                requestHeader.setBornTimestamp(System.currentTimeMillis());
                requestHeader.setFlag(msg.getFlag());
                //设置消息属性,里面包括了 tag等信息
                requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
                requestHeader.setReconsumeTimes(0);
                requestHeader.setUnitMode(this.isUnitMode());
                //设置是否为批量消息
                requestHeader.setBatch(msg instanceof MessageBatch);

开始发送消息

    //发送结果
 SendResult sendResult = null;
                switch (communicationMode) {
                    //同步模式下发送消息
                    case ASYNC:
                        Message tmpMessage = msg;
                        boolean messageCloned = false;
                        //消息是否被压缩过 4k
                        if (msgBodyCompressed) {
                            //If msg body was compressed, msgbody should be reset using prevBody.
                            //Clone new message using commpressed message body and recover origin massage.
                            //Fix bug:https://github.com/apache/rocketmq-externals/issues/66
                            tmpMessage = MessageAccessor.cloneMessage(msg);
                            messageCloned = true;
                            msg.setBody(prevBody);
                        }
                    
                        if (topicWithNamespace) {
                            if (!messageCloned) {
                                tmpMessage = MessageAccessor.cloneMessage(msg);
                                messageCloned = true;
                            }
                            msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
                        }
                        
                        //一共花费了多长时间
                        long costTimeAsync = System.currentTimeMillis() - beginStartTime;
                        //超出了默认的超时 时间,就会抛出异常
                        if (timeout < costTimeAsync) {
                            throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                        }
                        //开始发送消息
                        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                            brokerAddr,
                            mq.getBrokerName(),
                            tmpMessage,
                            requestHeader,
                            timeout - costTimeAsync,
                            communicationMode,
                            sendCallback,
                            topicPublishInfo,
                            this.mQClientFactory,
                            this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
                            context,
                            this);
                        break;

发送消息的核心方法解析

org.apache.rocketmq.client.impl.MQClientAPIImpl#sendMessage(java.lang.String, java.lang.String, org.apache.rocketmq.common.message.Message, org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader, long, org.apache.rocketmq.client.impl.CommunicationMode, org.apache.rocketmq.client.producer.SendCallback, org.apache.rocketmq.client.impl.producer.TopicPublishInfo, org.apache.rocketmq.client.impl.factory.MQClientInstance, int, org.apache.rocketmq.client.hook.SendMessageContext, org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl)

 //开始发送消息
                        sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                            brokerAddr,
                            mq.getBrokerName(),
                            tmpMessage,
                            requestHeader,
                            timeout - costTimeAsync,
                            communicationMode,
                            sendCallback,
                            topicPublishInfo,
                            this.mQClientFactory,
                            this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
                            context,
                            this);

由于本节讨论的均为同步模式,则以同步模式分析

//用于消息发送超时判断
long beginStartTime = System.currentTimeMillis();
        RemotingCommand request = null;
        if (sendSmartMsg || msg instanceof MessageBatch) {
            SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
            request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
        } else {
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
        }
        //请求里面设置请求的消息的字节数组
        request.setBody(msg.getBody());

        switch (communicationMode) {
            case ONEWAY:
                this.remotingClient.invokeOneway(addr, request, timeoutMillis);
                return null;
            case ASYNC:
                final AtomicInteger times = new AtomicInteger();
                //消息超时校验
                long costTimeAsync = System.currentTimeMillis() - beginStartTime;
                if (timeoutMillis < costTimeAsync) {
                    throw new RemotingTooMuchRequestException("sendMessage call timeout");
                }
//发送同步消息
                this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance,
                    retryTimesWhenSendFailed, times, context, producer);
                return null;

同步发送消息开始 使用  org.apache.rocketmq.client.impl.MQClientAPIImpl#sendMessageAsync

 RemotingCommand response = responseFuture.getResponseCommand();
                if (null == sendCallback && response != null) {

                    try {
 //使用MQClientAPI来发送消息,在MQ中都是,客户端和服务器之间的通信都是通过 MQClientAPIImpl 来的
                        SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
                        if (context != null && sendResult != null) {
                            context.setSendResult(sendResult);
                            context.getProducer().executeSendMessageHookAfter(context);
                        }
                    } catch (Throwable e) {
                    }

                    producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
                    return;
                }

org.apache.rocketmq.client.impl.MQClientAPIImpl#processSendResponse

 private SendResult processSendResponse(
        final String brokerName,
        final Message msg,
        final RemotingCommand response
    ) throws MQBrokerException, RemotingCommandException {
        switch (response.getCode()) {
            case ResponseCode.FLUSH_DISK_TIMEOUT:
            case ResponseCode.FLUSH_SLAVE_TIMEOUT:
            case ResponseCode.SLAVE_NOT_AVAILABLE: {
            }
            case ResponseCode.SUCCESS: {
                SendStatus sendStatus = SendStatus.SEND_OK;
                switch (response.getCode()) {
                    case ResponseCode.FLUSH_DISK_TIMEOUT:
                        sendStatus = SendStatus.FLUSH_DISK_TIMEOUT;
                        break;
                    case ResponseCode.FLUSH_SLAVE_TIMEOUT:
                        sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT;
                        break;
                    case ResponseCode.SLAVE_NOT_AVAILABLE:
                        sendStatus = SendStatus.SLAVE_NOT_AVAILABLE;
                        break;
                    case ResponseCode.SUCCESS:
                        sendStatus = SendStatus.SEND_OK;
                        break;
                    default:
                        assert false;
                        break;
                }

                SendMessageResponseHeader responseHeader =
                    (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);

                //If namespace not null , reset Topic without namespace.
                String topic = msg.getTopic();
                if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) {
                    topic = NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace());
                }
                //组装有一个消息队列,(主题,broker服务器名称,消息队列序号)
                MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId());

                String uniqMsgId = MessageClientIDSetter.getUniqID(msg);
                //批量消息的处理
                if (msg instanceof MessageBatch) {
                    StringBuilder sb = new StringBuilder();
                    for (Message message : (MessageBatch) msg) {
                        sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(message));
                    }
                    uniqMsgId = sb.toString();
                }
                SendResult sendResult = new SendResult(sendStatus,
                    uniqMsgId,
                    responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset());
                //返回结果,设置一个事务id
                sendResult.setTransactionId(responseHeader.getTransactionId());
                String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION);
                String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH);
                if (regionId == null || regionId.isEmpty()) {
                    regionId = MixAll.DEFAULT_TRACE_REGION_ID;
                }
                if (traceOn != null && traceOn.equals("false")) {
                    sendResult.setTraceOn(false);
                } else {
                    sendResult.setTraceOn(true);
                }
                sendResult.setRegionId(regionId);
                return sendResult;
            }
            default:
                break;
        }

        throw new MQBrokerException(response.getCode(), response.getRemark());
    }

4:总结

脉络流程图

RocketMQ源码解析之消息发送(二)_第1张图片

 

 

你可能感兴趣的:(RocketMQ,RockerMQ消息发送流程)