RocketMQ(3)- RockerMQ 消息发送

DefaultMQProducer 消息发送

1 消息发送方式

RocketMQ 消息发送主要分为三种方式:

  • 同步:producer 向 MQ 执行发送消息 API 时,同步等待,直到消息服务器返回发送结果。
  • 异步:producer 向 MQ 执行发送消息 API 时,指定消息发送成功后的回调函数,然后调用消息发送 API后,立即返回,消息发送者线程不阻塞,直到运行结束,消息发送成功或者失败的回调任务在新的线程中执行。
  • 单向:producer 向 MQ 执行发送消息 API 时,直接返回,不等待消息服务器的结果,并且也不注册回调函数。

2 启动 producer

producer 的启动很简洁,步骤如下,producer 具体作用类分为 DefaultMQProducerTransactionMQProducer, TransactionMQProducer 继承了 ``DefaultMQProducer, 增加了事务消息的处理逻辑。

// 实例化 producer,定义好 producerGroup
DefaultMQProducer producer = new DefaultMQProducer("quickstart");
// 设置 namesrv 地址
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动 prodcuer
producer.start();

2.1 producer 启动过程

org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#start(boolean): 启动 producer。

public void start(final boolean startFactory) throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            // 检查 productGroup 是否合法
            this.checkConfig();
            // 修改 producer 的instanceName 为进程id
            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                this.defaultMQProducer.changeInstanceNameToPID();
            }
            // 创建 MQClientInstance 实例,MQClientManager 维护 factoryTable 缓存表,clientId:MQClientInstance
            this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook);
            // 注册 producer, 将 producer 加入到 MQClientInstance 实例中管理,方便后续网络调用请求,心跳检测等
            boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);         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();
}

RocketMQ(3)- RockerMQ 消息发送_第1张图片

3 消息发送基本流程

消息发送流程主要分为:验证消息、查找路由、消息发送。org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send: 默认消息发送以同步方式进行,默认超时时间为3s。

public SendResult send(Message msg,
                       long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}

org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl : 默认发送消息逻辑核心。流程图如下。

RocketMQ(3)- RockerMQ 消息发送_第2张图片

3.1 校验 prodcuer 及消息

org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#makeSureStateOK
// 校验 Producer 是否处于 running 状态
private void makeSureStateOK() throws MQClientException {
    if (this.serviceState != ServiceState.RUNNING) {
        throw new MQClientException("The producer service state not OK, "
                                    + this.serviceState
                                    + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
                                    null);
    }
}

org.apache.rocketmq.client.Validators#checkMessage
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");
    }
    // 消息体大小不超过 1024 * 1024 * 4; // 4M
    if (msg.getBody().length > defaultMQProducer.getMaxMessageSize()) {
        throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL,
                                    "the message body size over max value, MAX: " + defaultMQProducer.getMaxMessageSize());
    }
}

3.2 查询 topic 路由信息

org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#tryToFindTopicPublishInfo:这里是根据 topic 获取具体的路由信息,也就是将消息发送到那个 broker 上。producer 缓存了 topic 的路由信息,如果该路由信息中包含了消息队列,则直接返回该路由信息,如果没有缓存或没有包含消息队列, 则向 nameServer 查询该 topic 路由信息,如果最终未找到路由信息,则抛出异常:无法找到主题相关路由信息异常

public class TopicPublishInfo {
    // 是否是顺序消息
    private boolean orderTopic = false;
    // 是否有 topic 的路由信息
    private boolean haveTopicRouterInfo = false;
    // topic 对应的消息队列
    private List<MessageQueue> messageQueueList = new ArrayList<MessageQueue>();
    // 每选择一次则+1,用于后续轮询队列。
    private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();
    // topic 的路由信息
    private TopicRouteData topicRouteData;
}

private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
    // 获取的 topic 路由信息不存在或者不可用
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
        // 更新 topic 路由信息
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        // 再从 topicPublishInfoTable 获取,如果获取到的可用,表示上一步操作执行成功
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
    }
    // 若获取的 Topic发布信息时候可用,则返回
    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
        return topicPublishInfo;
    } else {
        // 用于 Topic发布信息不存在 && Broker支持自动创建Topic
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    }
}
  1. 从 Namesrv 无法获取时,使用 DefaultMQProducer#createTopicKey 对应的 Topic发布信息。目的是当 Broker 开启自动创建 Topic开关时,Broker 接收到消息后自动创建Topic。
org.apache.rocketmq.client.impl.factory.MQClientInstance#updateTopicRouteInfoFromNameServer(java.lang.String, boolean, org.apache.rocketmq.client.producer.DefaultMQProducer)
if (isDefault && defaultMQProducer != null) {
    topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),
                                                                                 1000 * 3);
    if (topicRouteData != null) {
        for (QueueData data : topicRouteData.getQueueDatas()) {
            int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
            data.setReadQueueNums(queueNums);
            data.setWriteQueueNums(queueNums);
        }
    }
} else {
    // 从 nameserver 中根据 topic 获取对应的路由信息,超时时间为3s
    topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);
}
  1. 如果查询到路由信息,则与 client 中缓存的路由信息进行对比,判断路由是否发生变化,如果未发生变化,那么再将所有的 producer 和 consumer 中 topic 路由信息进行遍历,确定是否发生变化,如果未发生变化,则直接返回 false。
// 查看 topic 路由是否变化,如果变化则进行更新
TopicRouteData old = this.topicRouteTable.get(topic);
boolean changed = topicRouteDataIsChange(old, topicRouteData);
if (!changed) {
    changed = this.isNeedUpdateTopicRouteInfo(topic);
} else {
    log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData);
}
  1. 如果存在变化,则更新所有中包含 topic、broker 信息的缓存。
if (changed) {
    TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData();
    // 更新 brokerAddrTable
    for (BrokerData bd : topicRouteData.getBrokerDatas()) {
        this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
    }

    // Update Pub info
    // 更新 producer 中 topic 的路由信息
    {
        TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData);
        publishInfo.setHaveTopicRouterInfo(true);
        Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, MQProducerInner> entry = it.next();
            MQProducerInner impl = entry.getValue();
            if (impl != null) {
                impl.updateTopicPublishInfo(topic, publishInfo);
            }
        }
    }

    // Update sub info
    // 更新 consumer 中 topic 的路由信息
    {
        Set<MessageQueue> subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData);
        Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, MQConsumerInner> entry = it.next();
            MQConsumerInner impl = entry.getValue();
            if (impl != null) {
                impl.updateTopicSubscribeInfo(topic, subscribeInfo);
            }
        }
    }
    log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData);
    this.topicRouteTable.put(topic, cloneTopicRouteData);
    return true;
}

3.3 发送消息前操作

根据上一步 tryToFindTopicPublishInfo 方法会返回 topic 的路由信息,如果找不到,则直接抛出异常,否则根据 topic 的路由信息,选择发送的消息队列。

if (topicPublishInfo != null && topicPublishInfo.ok()) {
	// 最后选择消息要发送到的队列
	boolean callTimeout = false;
	MessageQueue mq = null;
	Exception exception = null;
	// 最后一次发送结果
	SendResult sendResult = null;
	// 同步多次调用, 重试机制,总共发送3次
	int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
	int times = 0;
	String[] brokersSent = new String[timesTotal];
	// 循环调用发送消息,直到成功或者失败次数达到3次
	for (; times < timesTotal; times++) {
		String lastBrokerName = null == mq ? null : mq.getBrokerName();
		// 选择消息要发送到的队列,每次选择都是从 messageQueueList 中从0开始选择有用的 broker
		MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
		if (mqSelected != null) {
			mq = mqSelected;
			brokersSent[times] = mq.getBrokerName();
			beginTimestampPrev = System.currentTimeMillis();
			long costTime = beginTimestampPrev - beginTimestampFirst;
			if (timeout < costTime) {
				callTimeout = true;
				break;
			}
			// 调用发送消息核心方法
			sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
			endTimestamp = System.currentTimeMillis();
			// 更新Broker可用性信息
			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;
			}
		} else {
			break;
		}
	}
	// 走到这里的,发送方式只能是同步,返回发送结果
	if (sendResult != null) {
		return sendResult;
	}
}
  1. 首先消息发送端采用重试机制,由 retryTimesWhenSendFailed 指定同步方式重试次数,异步重试机制在收到消息发送结构后执行回调之前进行重试。由retryTimesWhenSend-AsyncFailed指定,接下来就是循环执行,选择消息队列、发送消息,发送成功则返回,收到异常则重试。选择消息队列有两种方式:
    • sendLatencyFaultEnable=false,默认不启用Broker故障延迟机制。
    • sendLatencyFaultEnable=true,启用Broker故障延迟机制。
org.apache.rocketmq.client.latency.MQFaultStrategy#selectOneMessageQueue
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
	// 启用Broker故障延迟机制
	if (this.sendLatencyFaultEnable) {
		try {
			// 获取 brokerName=lastBrokerName && 可用的一个消息队列
			int index = tpInfo.getSendWhichQueue().getAndIncrement();
			for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
				int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
				if (pos < 0)
					pos = 0;
				MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
				if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
					if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
						return mq;
				}
			}
			// 走到这里,表示上面没有获取到可用性的消息队列
			// 选择一个相对好的broker,并获得其对应的一个消息队列,不考虑该队列的可用性
			final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
			int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
			if (writeQueueNums > 0) {
				final MessageQueue mq = tpInfo.selectOneMessageQueue();
				if (notBestBroker != null) {
					mq.setBrokerName(notBestBroker);
					mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
				}
				return mq;
			} else {
				latencyFaultTolerance.remove(notBestBroker);
			}
		} catch (Exception e) {
			log.error("Error occurred when selecting message queue", e);
		}
		// 选择一个消息队列,不考虑队列的可用性
		return tpInfo.selectOneMessageQueue();
	}
	// 获得一个brokerName 不等于 lastBrokerName 的消息队列,不考虑该队列的可用性
	return tpInfo.selectOneMessageQueue(lastBrokerName);
}
org.apache.rocketmq.client.impl.producer.TopicPublishInfo#selectOneMessageQueue()
public MessageQueue selectOneMessageQueue() {
	int index = this.sendWhichQueue.getAndIncrement();
	int pos = Math.abs(index) % this.messageQueueList.size();
	if (pos < 0)
		pos = 0;
	return this.messageQueueList.get(pos);
}
org.apache.rocketmq.client.impl.producer.TopicPublishInfo#selectOneMessageQueue(java.lang.String)
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
	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;
			MessageQueue mq = this.messageQueueList.get(pos);
			// 获取和上一个不一致的 broker
			if (!mq.getBrokerName().equals(lastBrokerName)) {
				return mq;
			}
		}
		return selectOneMessageQueue();
	}
}

由于重试机制,可能会多次执行选择消息队列这个方法,lastBrokerName 就是上一次选择执行发送消息失败的 broker,那么在下一次选择时,则规避上一次失败的 broker。

  1. Broker 的故障延迟机制
  2. 根据消息队列进行轮询获取一个消息队列。
  3. latencyFaultTolerance.isAvailable:验证该消息队列是否可用。
  4. 如果返回的 mq 可用,调用 latencyFaultTolerance.remove 移除 broker,表明 broker 故障已经恢复。

3.4 消息发送

前面已经找到了发送的 broker 和 messageQueue, 那么接下来就是调用org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl,这最核心的实际发送的过程。方法参数意义分别是:

  • final Message msg, // 待发送的消息
  • final MessageQueue mq, // 消息将发送到该消息队列上
  • final CommunicationMode communicationMode, // 消息发送模式:ASYNC SYNC ONEWAY
  • final SendCallback sendCallback, // 异步消息回调函数
  • final TopicPublishInfo topicPublishInfo, // 主题路由信息
  • final long timeout // 整个过程的超时时间

流程的主要步骤如下。

  1. 根据 MessageQueue 获取 Broker 的网络地址。如果 MQClientInstance 的 brokerAddrTable 未缓存该Broker 的信息,则从 NameServer 主动更新一下 topic 的路由信息。如果路由更新后还是找不到 Broker 信息,则抛出 MQClientException,提示Broker 不存在。

    // 从 brokerAddrTable 缓存中获取 broker地址, broker 为 master 节点
    String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
    if (null == brokerAddr) {
    	// 从 nameserver 中获取 topic 信息,前面分析过这个方法
    	// 它会将重新获取到 topic 的路由信息,更新到每个缓存中
    	tryToFindTopicPublishInfo(mq.getTopic());
    	brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
    }
    
  2. 如果 vipchannel,那么将 broker 的 port - 2,然后为消息分配全局唯一ID,如果消息体默认超过4K(compressMsgBodyOverHowmuch),会对消息体采用zip压缩,并设置消息的系统标记为MessageSysFlag.COMPRESSED_FLAG。如果是事务Prepared消息,则设置消息的系统标记为MessageSysFlag.TRANSACTION_PREPARED_TYPE。

    brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);
    byte[] prevBody = msg.getBody();
    //for MessageBatch,ID has been set in the generating process
    // 如果不是批量消息,则设置唯一编号
    if (!(msg instanceof MessageBatch)) {
    	MessageClientIDSetter.setUniqID(msg);
    }
    int sysFlag = 0;
    boolean msgBodyCompressed = false;
    // 消息压缩
    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)) {
    	sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
    }
    
  3. 如果注册了消息发送前钩子函数,则执行增强的逻辑。然后构建消息发送请求包

// hook:发送消息前逻辑
if (this.hasSendMessageHook()) {
	context = new SendMessageContext();
	context.setProducer(this);
	context.setProducerGroup(this.defaultMQProducer.getProducerGroup());
	context.setCommunicationMode(communicationMode);
	context.setBornHost(this.defaultMQProducer.getClientIP());
	context.setBrokerAddr(brokerAddr);
	context.setMessage(msg);
	context.setMq(mq);
	String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
	if (isTrans != null && isTrans.equals("true")) {
		// 事务消息,设置消息类型为半消息
		context.setMsgType(MessageType.Trans_Msg_Half);
	}

	if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
		context.setMsgType(MessageType.Delay_Msg);
	}
	this.executeSendMessageHookBefore(context);
}
  1. 调用 org.apache.rocketmq.client.impl.MQClientAPIImpl#sendMessage 执行最终发送消息的操作,如果注册了消息发送后的钩子函数,执行 org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#executeSendMessageHookAfter 方法。

    // hook:发送消息后逻辑
    if (this.hasSendMessageHook()) {
    	context.setSendResult(sendResult);
    	this.executeSendMessageHookAfter(context);
    }
    
    public void executeSendMessageHookAfter(final SendMessageContext context) {
    	if (!this.sendMessageHookList.isEmpty()) {
    		for (SendMessageHook hook : this.sendMessageHookList) {
    			try {
    				hook.sendMessageAfter(context);
    			} catch (Throwable e) {
    				log.warn("failed to executeSendMessageHookAfter", e);
    			}
    		}
    	}
    }
    

3.5 同步发送

MQ客户端发送消息的入口是 MQClientAPIImpl#sendMessage。请求命令是Request-Code.SEND_MESSAGE,我们可以找到该命令的处理类: org.apache.rocketmq.broker.processor.SendMessageProcessor。入口方法在 SendMessageProcessor#sendMessage。broker 接收到 producer 发送的消息发送请求,根据 request_code 进行处理.

  1. org.apache.rocketmq.broker.processor.AbstractSendMessageProcessor#msgCheck: 检查消息发送是否合法

    1. 判断 broker 是否具有写权限
    2. 检查 topic 是否可以进行发送
    3. 检查队列是否合法
    protected RemotingCommand msgCheck(final ChannelHandlerContext ctx,
                                       final SendMessageRequestHeader requestHeader, final RemotingCommand response) {
        // broker 是否有写权限
        if (!PermName.isWriteable(this.brokerController.getBrokerConfig().getBrokerPermission())
            && this.brokerController.getTopicConfigManager().isOrderTopic(requestHeader.getTopic())) {
            response.setCode(ResponseCode.NO_PERMISSION);
            response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
                               + "] sending message is forbidden");
            return response;
        }
        // topic 是否可以进行消息发送
        if (!this.brokerController.getTopicConfigManager().isTopicCanSendMessage(requestHeader.getTopic())) {
            String errorMsg = "the topic[" + requestHeader.getTopic() + "] is conflict with system reserved words.";
            log.warn(errorMsg);
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark(errorMsg);
            return response;
        }
        // 检查 topicConfig 配置
        TopicConfig topicConfig =
            this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
        if (null == topicConfig) {
            int topicSysFlag = 0;
            if (requestHeader.isUnitMode()) {
                if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    topicSysFlag = TopicSysFlag.buildSysFlag(false, true);
                } else {
                    topicSysFlag = TopicSysFlag.buildSysFlag(true, false);
                }
            }
    
            log.warn("the topic {} not exist, producer: {}", requestHeader.getTopic(), ctx.channel().remoteAddress());
            topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageMethod(
                requestHeader.getTopic(),
                requestHeader.getDefaultTopic(),
                RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                requestHeader.getDefaultTopicQueueNums(), topicSysFlag);
    
            if (null == topicConfig) {
                if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    topicConfig =
                        this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(
                        requestHeader.getTopic(), 1, PermName.PERM_WRITE | PermName.PERM_READ,
                        topicSysFlag);
                }
            }
    
            if (null == topicConfig) {
                response.setCode(ResponseCode.TOPIC_NOT_EXIST);
                response.setRemark("topic[" + requestHeader.getTopic() + "] not exist, apply first please!"
                                   + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
                return response;
            }
        }
    
        int queueIdInt = requestHeader.getQueueId();
        // 检查队列是否合法
        int idValid = Math.max(topicConfig.getWriteQueueNums(), topicConfig.getReadQueueNums());
        if (queueIdInt >= idValid) {
            String errorInfo = String.format("request queueId[%d] is illegal, %s Producer: %s",
                                             queueIdInt,
                                             topicConfig.toString(),
                                             RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
    
            log.warn(errorInfo);
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark(errorInfo);
    
            return response;
        }
        return response;
    }
    
  2. 调用 org.apache.rocketmq.broker.processor.SendMessageProcessor#handleRetryAndDLQ: 如果消息重试次数超过允许的最大重试次数,消息将进入 DLD 延迟队列, 延时队列主题: %DLQ%+消费组名.

  3. 最后调用 org.apache.rocketmq.store.DefaultMessageStore#putMessage 进行消息存储,这里是 rocketmq-strore 包中的内容.

3.6 异步发送

消息异步发送是指消息生产者调用发送的API后,无须阻塞等待消息服务器返回本次消息发送结果,只需要提供一个回调函数,供消息发送客户端在收到响应结果回调。异步方式相比同步方式,消息发送端的发送性能会显著提高,但为了保护消息服务器的负载压力,RocketMQ 对消息发送的异步消息进行了并发控制,通过参数clientAsyncSemaphoreValue 来控制,默认为65535。异步消息发送虽然也可以通过DefaultMQProducer#retryTimes-WhenSendAsyncFailed 属性来控制消息重试次数,但是重试的调用人口是在收到服务端响应包时进行的,如果出现网络异常、网络超时等将不会重试。

3.7 单向发送

单向发送是指消息生产者调用消息发送的API后,无须等待消息服务器返回本次消息发送结果,并且无须提供回调函数,表示消息发送压根就不关心本次消息发送是否成功,其实现原理与异步消息发送相同,只是消息发送客户端在收到响应结果后什么都不做而已,并且没有重试机制。

3.8 批量消息发送

批量消息发送是将同一主题的多条消息一起打包发送到消息服务端,减少网络调用次数,提高网络传输效率。当然,并不是在同一批次中发送的消息数量越多性能就越好,其判断依据是单条消息的长度,如果单条消息内容比较长,则打包多条消息发送会影响其他线程发送消息的响应时间,并且单批次消息发送总长度不能超过DefaultMQProducer#maxMessageSize。

  1. RocketMQ 采取对单条消息内容使用固定格式进行存储,那么在批量发送消息时,就可以从body 中,准确取出每一条的内容

RocketMQ(3)- RockerMQ 消息发送_第3张图片

你可能感兴趣的:(RocketMQ,java,RocketMQ,消息队列)