RocketMQ 发送消息

概览

RocketMQ 支持 种消息发送方式 :同步(sync)、 异步(async)、单向(oneway)。

Message

消息封装类 org.apache.rocketmq.common.message.Message


public class Message implements Serializable {
    private static final long serialVersionUID = 8445773977080406428L;

    private String topic;
    private int flag;
    // {MIN_OFFSET=0, MAX_OFFSET=3490, CONSUME_START_TIME=1601275494759, UNIQ_KEY=0A0116BE573918B4AAC28E35DD6503D5, CLUSTER=DefaultCluster, WAIT=true, TAGS=TagA}
    // WAIT 等待消息存储OK
    // TAGS 消息过滤标签
    // UNIO_KEY Message 索引键,RocketMQ 可以根据这些 key 快速索引到消息
    private Map<String, String> properties;
    private byte[] body;
    private String transactionId;
}
Message 字符串
{
	topic = 'TopicTest', flag = 0, properties = {
		MIN_OFFSET = 0,
		MAX_OFFSET = 3437,
		CONSUME_START_TIME = 1601275494592,
		UNIQ_KEY = 0 A0116BE573918B4AAC28E35DCBE02F0,
		CLUSTER = DefaultCluster,
		WAIT = true,
		TAGS = TagA
	}, 
    body = [72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 55, 53, 50], 
    transactionId = 'null'
}
DefaultMQProducer 消息发送者

DefaultMQProducer 是发送消息的入口和发送消息的类,各种重载的 send() 方法,选择适合自己的发送方法。这个类在配置和启动完成之后可以被当做线程安全的类来使用。

它实现了 MQAdmin 、MQProducer 接口,提供了消息的各种方法。

MQAdmin

最简单的消息管理接口

public interface MQAdmin {
    /**
     * 创建主题
     *
     * Creates an topic
     *
     * @param key accesskey 目前为实际作用,可以与 newTopic 相同
     * @param newTopic topic name 主题名称
     * @param queueNum topic's queue number 队列数量
     */
    void createTopic(final String key, final String newTopic, final int queueNum)
        throws MQClientException;

    /**
     * 创建主题
     *
     * Creates an topic
     *
     * @param key accesskey
     * @param newTopic topic name
     * @param queueNum topic's queue number
     * @param topicSysFlag topic system flag 主题系统标签,默认为 0
     */
    void createTopic(String key, String newTopic, int queueNum, int topicSysFlag)
        throws MQClientException;

    /**
     * 根据时间戳从队列中查找其偏移量
     * Gets the message queue offset according to some time in milliseconds<br>
     * be cautious to call because of more IO overhead
     *
     * @param mq Instance of MessageQueue
     * @param timestamp from when in milliseconds.
     * @return offset
     */
    long searchOffset(final MessageQueue mq, final long timestamp) throws MQClientException;

    /**
     * 查找该消息队列中最大的物理偏移量
     *
     * Gets the max offset
     *
     * @param mq Instance of MessageQueue
     * @return the max offset
     */
    long maxOffset(final MessageQueue mq) throws MQClientException;

    /**
     * Gets the minimum offset
     *
     * @param mq Instance of MessageQueue
     * @return the minimum offset
     */
    long minOffset(final MessageQueue mq) throws MQClientException;

    /**
     * 得到消息队列的最早存储时间
     * Gets the earliest stored message time
     *
     * @param mq Instance of MessageQueue
     * @return the time in microseconds
     */
    long earliestMsgStoreTime(final MessageQueue mq) throws MQClientException;

    /**
     * 根据消息Id 查找消息
     * Query message according to message id
     *
     * @param offsetMsgId message id
     * @return message
     */
    MessageExt viewMessage(final String offsetMsgId) throws RemotingException, MQBrokerException,
        InterruptedException, MQClientException;

    /**
     * 查询消息
     * Query messages
     *
     * @param topic message topic
     * @param key message key index word 消息索引字段
     * @param maxNum max message number 本次最多取出消息条数
     * @param begin from when 开始时间
     * @param end to when 结束时间
     * @return Instance of QueryResult
     */
    QueryResult queryMessage(final String topic, final String key, final int maxNum, final long begin,
        final long end) throws MQClientException, InterruptedException;

    /**
     * 查找消息
     * @return The {@code MessageExt} of given msgId
     */
    MessageExt viewMessage(String topic,
        String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException;

}
MQProducer

MQProducer 是消息发送功能的基本接口。

public interface MQProducer extends MQAdmin {
    void start() throws MQClientException;

    void shutdown();
    /**
     * 查找该主题下所有消息队列
     */
    List<MessageQueue> fetchPublishMessageQueues(final String topic) throws MQClientException;

    SendResult send(final Message msg) throws MQClientException, RemotingException, MQBrokerException,
        InterruptedException;

    /**
     * 发送消息
     * @param msg
     * @param timeout 超时时间,超出后抛异常
     * @return
     * @throws MQClientException
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    SendResult send(final Message msg, final long timeout) throws MQClientException,
        RemotingException, MQBrokerException, InterruptedException;

    void send(final Message msg, final SendCallback sendCallback) throws MQClientException,
        RemotingException, InterruptedException;

    void send(final Message msg, final SendCallback sendCallback, final long timeout)
        throws MQClientException, RemotingException, InterruptedException;

    void sendOneway(final Message msg) throws MQClientException, RemotingException,
        InterruptedException;

    SendResult send(final Message msg, final MessageQueue mq) throws MQClientException,
        RemotingException, MQBrokerException, InterruptedException;

    SendResult send(final Message msg, final MessageQueue mq, final long timeout)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException;

    /**
     * 异步发送消息
     * @param msg
     * @param mq
     * @param sendCallback 异步回调函数
     * @throws MQClientException
     * @throws RemotingException
     * @throws InterruptedException
     */
    void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback)
        throws MQClientException, RemotingException, InterruptedException;

    void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback, long timeout)
        throws MQClientException, RemotingException, InterruptedException;

    void sendOneway(final Message msg, final MessageQueue mq) throws MQClientException,
        RemotingException, InterruptedException;

    /**
     *
     * @param msg
     * @param selector 消息队列选择器
     * @param arg
     * @return
     * @throws MQClientException
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException;

    SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg,
        final long timeout) throws MQClientException, RemotingException, MQBrokerException,
        InterruptedException;

    void send(final Message msg, final MessageQueueSelector selector, final Object arg,
        final SendCallback sendCallback) throws MQClientException, RemotingException,
        InterruptedException;

    void send(final Message msg, final MessageQueueSelector selector, final Object arg,
        final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException,
        InterruptedException;

    void sendOneway(final Message msg, final MessageQueueSelector selector, final Object arg)
        throws MQClientException, RemotingException, InterruptedException;

    /**
     * 发送事务消息
     * @param msg
     * @param tranExecuter
     * @param arg
     * @return
     * @throws MQClientException
     */
    TransactionSendResult sendMessageInTransaction(final Message msg,
        final LocalTransactionExecuter tranExecuter, final Object arg) throws MQClientException;

    TransactionSendResult sendMessageInTransaction(final Message msg,
        final Object arg) throws MQClientException;

    //for batch
    SendResult send(final Collection<Message> msgs) throws MQClientException, RemotingException, MQBrokerException,
        InterruptedException;

    SendResult send(final Collection<Message> msgs, final long timeout) throws MQClientException,
        RemotingException, MQBrokerException, InterruptedException;

    SendResult send(final Collection<Message> msgs, final MessageQueue mq) throws MQClientException,
        RemotingException, MQBrokerException, InterruptedException;

    SendResult send(final Collection<Message> msgs, final MessageQueue mq, final long timeout)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException;

    //for rpc
    Message request(final Message msg, final long timeout) throws RequestTimeoutException, MQClientException,
        RemotingException, MQBrokerException, InterruptedException;

    void request(final Message msg, final RequestCallback requestCallback, final long timeout)
        throws MQClientException, RemotingException, InterruptedException, MQBrokerException;

    Message request(final Message msg, final MessageQueueSelector selector, final Object arg,
        final long timeout) throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException,
        InterruptedException;

    void request(final Message msg, final MessageQueueSelector selector, final Object arg,
        final RequestCallback requestCallback,
        final long timeout) throws MQClientException, RemotingException,
        InterruptedException, MQBrokerException;

    Message request(final Message msg, final MessageQueue mq, final long timeout)
        throws RequestTimeoutException, MQClientException, RemotingException, MQBrokerException, InterruptedException;

    void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException;
}
DefaultMQProducer

DefaultMQProducer 定义了消息发送的接口方法,具体细节实现由 DefaultMQProducerImpl 完成。
它也定义了一些发送的属性设置。

public class DefaultMQProducer extends ClientConfig implements MQProducer {

    /**
     * Wrapping internal implementations for virtually all methods presented in this class.
     */
    protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
    private final InternalLogger log = ClientLogger.getLog();
    /**
     * 生产者组,消息服务器在会查事务状态时会随机选择该组中任何一个生产者发起事务会查请求。
     * Producer group conceptually aggregates all producer instances of exactly same role, which is particularly
     * important when transactional messages are involved. </p>
     *
     * For non-transactional messages, it does not matter as long as it's unique per process. </p>
     *
     * See {@linktourl http://rocketmq.apache.org/docs/core-concept/} for more discussion.
     */
    private String producerGroup;

    /**
     * 默认创建的topic
     * Just for testing or demo program
     */
    private String createTopicKey = TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC;

    /**
     * 默认主题在一个 Broker 中队列数量
     * Number of queues to create per default topic.
     */
    private volatile int defaultTopicQueueNums = 4;

    /**
     * 发送消息默认超时时间
     * Timeout for sending messages.
     */
    private int sendMsgTimeout = 3000;

    /**
     * 消息体超过该值,则启动压缩 4k
     * Compress message body threshold, namely, message body larger than 4k will be compressed on default.
     */
    private int compressMsgBodyOverHowmuch = 1024 * 4;

    /**
     * 同步方式发送消息重试次数,默认为2,总共执行3次
     * Maximum number of retry to perform internally before claiming sending failure in synchronous mode. </p>
     *
     * 造成消息的重复发送,需要开发者信息处理
     *
     * This may potentially cause message duplication which is up to application developers to resolve.
     */
    private int retryTimesWhenSendFailed = 2;

    /**
     * 异步方式发送消息发送次数
     * Maximum number of retry to perform internally before claiming sending failure in asynchronous mode. </p>
     * 存在多次发送重复消息的问题
     * This may potentially cause message duplication which is up to application developers to resolve.
     */
    private int retryTimesWhenSendAsyncFailed = 2;

    /**
     * 消息重试时选择另外一个 Broker 时,是否不等待存储结果就返回
     * Indicate whether to retry another broker on sending failure internally.
     */
    private boolean retryAnotherBrokerWhenNotStoreOK = false;

    /**
     * 允许发送的最大消息长度,默认 4M
     * Maximum allowed message size in bytes.
     */
    private int maxMessageSize = 1024 * 1024 * 4; // 4M
} 

Producer 启动流程

DefaultMQProducer#start() 方法开始启动 Producer。

/**
 * Start this producer instance. </p>
 *
 * <strong> Much internal initializing procedures are carried out to make this instance prepared, thus, it's a must
 * to invoke this method before sending or querying messages. </strong> </p>
 *
 * @throws MQClientException if there is any unexpected error.
 */
@Override
public void start() throws MQClientException {
    this.setProducerGroup(withNamespace(this.producerGroup));
    this.defaultMQProducerImpl.start();
    if (null != traceDispatcher) {
        try {
            traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
        } catch (MQClientException e) {
            log.warn("trace dispatcher start failed ", e);
        }
    }
}

// 消息生产者启动流程
public void start() throws MQClientException {
    this.start(true);
}

public void start(final boolean startFactory) throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            // 检查 productGroup 是否符合要去;并改变生产者的 instanceName 为进程 ID
            this.checkConfig();

            if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
                this.defaultMQProducer.changeInstanceNameToPID();
            }
            // 串讲 MQClientInstance 实例。整个 JVM 实例中只存在一个 MQClientManager 实例
            // MQClientManager 维护一个 MQClientInstance 缓存表  ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable = new ConcurrentHashMap<String, MQClientInstance>();
            // 也就是同一个 clientId 只会创建一个 MQClientInstance。
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
            // 向 MQClientInstance 注册,将当前生产者加入到 MQClientInstance 管理中,方便后续调用网络请求,进行心跳检测等
            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);
            }

            this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
            // 启动 MQClientInstance,如果 MQClientInstance 已经启动,则不会再启动
            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();

    this.timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            try {
                RequestFutureTable.scanExpiredRequest();
            } catch (Throwable e) {
                log.error("scan RequestFutureTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
}

消息发送的基本流程

消息发送流程主要的步骤:验证消息、查找路由、消息发送 (包含异常处理机制)。

消息发送
# DefaultMQProducer#send
public SendResult send(
    Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    Validators.checkMessage(msg, this);
    msg.setTopic(withNamespace(msg.getTopic()));
    return this.defaultMQProducerImpl.send(msg);
}
# DefaultMQProducerImpl.send
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 {
    return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}

验证消息
// 检测生产者状态
this.makeSureStateOK();
// 检测消息
Validators.checkMessage(msg, this.defaultMQProducer);

/**
 * 发送的消息检测
 * @param msg
 * @param defaultMQProducer
 * @throws MQClientException
 */
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());
    Validators.isNotAllowedSendTopic(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: " + defaultMQProducer.getMaxMessageSize());
    }
}

查找Topic 路由信息

如果生产者中缓存了 topic 的路由信息,如果该路由信息包含了消息队列,则直接返回路由信息,如果没有缓存或没有包含消息队列,则向 NameServer 查询该 topic 的路由信息。如果最终未找到路由信息,则抛出异常。


/**
 * 查找 topic 的路由信息
 *
 * 如果生产者中缓存了 topic 的路由信息,如果该路由信息包含了消息队列,则直接返回路由信息,如果没有缓存或没有包含消息队列,则向 NameServer 查询该 topic 的路由信息。
 * 如果最终未找到路由信息,则抛出异常。
 *
 * @param topic
 * @return
 */
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
        // 存储这个 TopicPublishInfo
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
        // 第一次发送消息时,本地没有缓存 topic 路由信息,查询NameServer 尝试获取,如果路由信息未找到,在次尝试用默认主题 DefaultMQProducerImpl#createTopicKey 去查询,
        // 如果 BrokerConfig#autoCreateTopicEnable 为 true时,NameServer 将返回路由信息,如果 autoCreateTopicEnable 为false,将抛出无法找到 topic 路由异常。
        // updateTopicRouteInfoFromNameServer 方法 为消息生产者更新和维护路由缓存
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
    }
    // 包含路由信息或 messageQueueList 存在且 messageQueueList 不为空
    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
        return topicPublishInfo;
    } else {
        // 从 NameServer 获取路由信息并更新路由信息表 topicPublishInfoTable
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    }
}

// 从 NameServer 获取 topic 路由信息,并更新缓存
public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault,
    DefaultMQProducer defaultMQProducer) {
    try {
        if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
            try {
                TopicRouteData topicRouteData;
                // 如果 isDefault 为 true,则使用默认主题去查询,如果查询到路由信息,则替换路由信息中读写队列个数为消息生产者默认的队列个数(defaultTopicQueueNums);
                // 如果 isDefault 为 false,则使用参数 topic 去查询;如果未查询到路由信息,则返回 false,表示路由信息未变化。
                if (isDefault && defaultMQProducer != null) {
                    // defaultMQProducer.getCreateTopicKey() 创建默认 topic,查询路由信息
                    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 {
                    topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);
                }
                // 找到 topic 路由信息
                if (topicRouteData != null) {
                    // 如果路由信息找到,与本地缓存中的路由信息对比,判断路由信息是否发生了改变,如果为发生改变,则直接返回 false
                    TopicRouteData old = this.topicRouteTable.get(topic);
                    // 路由信息是否改变
                    boolean changed = topicRouteDataIsChange(old, topicRouteData);
                    // 没有改变
                    if (!changed) {
                        // 判断是否需要更新topic 的地址缓存表
                        changed = this.isNeedUpdateTopicRouteInfo(topic);
                    } else {
                        log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData);
                    }
                    // 路由新改变了
                    if (changed) {
                        TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData();

                        for (BrokerData bd : topicRouteData.getBrokerDatas()) {
                            this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
                        }
                        // 根据 topicRouteData 中的 List<QueueData> 转换成 topicPublishInfo 的 List<MessageQueue> 列表。
                        // Update Pub info
                        {
                            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) {
                                    // topic 下的路由信息集合 ConcurrentMap<String/* topic */, TopicPublishInfo> topicPublishInfoTable
                                    impl.updateTopicPublishInfo(topic, publishInfo);
                                }
                            }
                        }

                        // Update sub info
                        // 更新 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) {
                                    // 更新 RebalanceImpl#ConcurrentMap<String/* topic */, Set<MessageQueue>> topicSubscribeInfoTable
                                    impl.updateTopicSubscribeInfo(topic, subscribeInfo);
                                }
                            }
                        }
                        log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData);
                        // 然后会更新该 MQClientInstance 所管辖的所有消息发送关于 topic 的路由信息。
                        this.topicRouteTable.put(topic, cloneTopicRouteData);
                        return true;
                    }
                //    没有找到 路由信息,记录日志
                } else {
                    log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}", topic);
                }
            } catch (MQClientException e) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("updateTopicRouteInfoFromNameServer Exception", e);
                }
            } catch (RemotingException e) {
                log.error("updateTopicRouteInfoFromNameServer Exception", e);
                throw new IllegalStateException(e);
            } finally {
                this.lockNamesrv.unlock();
            }
        } else {
            log.warn("updateTopicRouteInfoFromNameServer tryLock timeout {}ms", LOCK_TIMEOUT_MILLIS);
        }
    } catch (InterruptedException e) {
        log.warn("updateTopicRouteInfoFromNameServer Exception", e);
    }

    return false;
}

消息队列选择

[
    {
        "brokerName": "broker-a",
        "queueId": 0
    },
    {
        "brokerName": "broker-a",
        "queueId": 1
    },
    {
        "brokerName": "broker-a",
        "queueId": 2
    },
    {
        "brokerName": "broker-a",
        "queueId": 3
    },
    {
        "brokerName": "broker-b",
        "queueId": 0
    },
    {
        "brokerName": "broker-b",
        "queueId": 1
    },
    {
        "brokerName": "broker-b",
        "queueId": 2
    },
    {
        "brokerName": "broker-b",
        "queueId": 3
    }
]

根据路由信息选择消息队列,返回的消息队列按照 broker、序号排序,如上图。那RocketMQ 如何选择消息队列呢?

首先消息发送端采用重试机制,由retryTimesWhenSendFailed 指定同步方式重试次数,异步重试机制在收到消息发送结构后执行回调之前进行重试。由 retryTimesWhenSendAsyncFailed 指定,接下来就是循环执行,选择消息队列、发送消息,发送成功则返回,收到异常则重试。
选择消息队列有两种方式。

  • sendLatencyFaultEnable = false,默认不启用 Broker 故障延迟机制。
  • sendLatencyFaultEnable = true,启用Broker 故障延迟机制。
消息队列选择
/**
 * 根据消息路由信息,选择 MessageQueue
 * @param tpInfo
 * @param lastBrokerName 上一次选择的执行发送消息失败的 Broker
 * @return
 */
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
    // Broker 故障延迟机制
    if (this.sendLatencyFaultEnable) {
        try {
            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;
                }
            }

            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();
    }
    // 非 Broker 故障延迟机制
    return tpInfo.selectOneMessageQueue(lastBrokerName);
}
默认机制
/**
 * 选择 MessageQueue
 * @param lastBrokerName 上一次选择的执行发送消息失败的 Broker
 * @return
 */
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
    if (lastBrokerName == null) {
        return selectOneMessageQueue();
    } else {
        // 循环遍历消息队列,依次取余,直到一个非上次发送消息失败的broker 为止
        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);
            if (!mq.getBrokerName().equals(lastBrokerName)) {
                return mq;
            }
        }
        // 规避上次发送消息失败的 broker 失败,默认选取一个余数作为队列下标
        return selectOneMessageQueue();
    }
}
    /**
 * 除以队列个数,取余数,选择队列
 * @return
 */
public MessageQueue selectOneMessageQueue() {
    // MessageQueue 自增队列
    int index = this.sendWhichQueue.getAndIncrement();
    //  除以队列个数,取余数,选择队列
    int pos = Math.abs(index) % this.messageQueueList.size();
    if (pos < 0)
        pos = 0;
    return this.messageQueueList.get(pos);
}

public int getQueueIdByBroker(final String brokerName) {
    for (int i = 0; i < topicRouteData.getQueueDatas().size(); i++) {
        final QueueData queueData = this.topicRouteData.getQueueDatas().get(i);
        if (queueData.getBrokerName().equals(brokerName)) {
            return queueData.getWriteQueueNums();
        }
    }

    return -1;
}
故障延迟机制

Broker 故障延迟规避机制,存储的是有故障的条目(broker)每一次Producer 发送消息时都会进行 MessageQueue 选择,如果一个Broker 宕机了,每次 MessageQueue 的选择都会去选择这个 Broker 下的消息队列,直接规避这个 Broker,使这个 Broker 中的 MessageQueue 不在消息队列中,从而,减少了选择 MessageQueue 失败的次数。

// Broker 故障延迟机制
if (this.sendLatencyFaultEnable) {
    try {
        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
            MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
            // 如果这个 MessageQueue 不在故障期,可用,直接返回
            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) {
            // 更新 MessageQueue 信息,返回 MessageQueue
            final MessageQueue mq = tpInfo.selectOneMessageQueue();
            if (notBestBroker != null) {
                mq.setBrokerName(notBestBroker);
                mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
            }
            return mq;
        } else {
            // 如果没有可写的 writeQueueNums,从隔离中移除这个 broker,然后等待这个 broker 有可以写的消息队列
            latencyFaultTolerance.remove(notBestBroker);
        }
    } catch (Exception e) {
        log.error("Error occurred when selecting message queue", e);
    }
    // 如果不行的话,默认选择一个队列
    return tpInfo.selectOneMessageQueue();
}
Broker 隔离

Broker 故障延迟规避机制,存储的是有故障的条目(broker)每一次Producer 发送消息时都会进行 MessageQueue 选择,如果一个Broker 宕机了,每次 MessageQueue 的选择都会去选择这个 Broker 下的消息队列,直接规避这个 Broker,使这个 Broker 中的 MessageQueue 不在消息队列中,从而,减少了选择 MessageQueue 失败的次数。

隔离策略
public class MQFaultStrategy {
    private final static InternalLogger log = ClientLogger.getLog();
    private final LatencyFaultTolerance<String> latencyFaultTolerance = new LatencyFaultToleranceImpl();
    // Broker 故障延迟机制
    private boolean sendLatencyFaultEnable = false;
    // 根据 currentLatency 本次消息发送延迟,从 latencyMax 尾部向前找到第一个比 currentLatency 小的索引 index,
    // 如果没有找到,返回 0。然后根据这个索引从 notAvailableDuration 数组中取出对应的时间,在这个时长内,Broker 将设置为不可用。
    // 举例:延迟时长为 500L,latencyMax 下标为 2,所以,broker 的隔离时长为 30000L
    private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
    private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};

    public long[] getNotAvailableDuration() {
        return notAvailableDuration;
    }

    public void setNotAvailableDuration(final long[] notAvailableDuration) {
        this.notAvailableDuration = notAvailableDuration;
    }

    public long[] getLatencyMax() {
        return latencyMax;
    }

    public void setLatencyMax(final long[] latencyMax) {
        this.latencyMax = latencyMax;
    }

    public boolean isSendLatencyFaultEnable() {
        return sendLatencyFaultEnable;
    }

    public void setSendLatencyFaultEnable(final boolean sendLatencyFaultEnable) {
        this.sendLatencyFaultEnable = sendLatencyFaultEnable;
    }

    /**
     * 根据消息路由信息,选择 MessageQueue
     * @param tpInfo
     * @param lastBrokerName 上一次选择的执行发送消息失败的 Broker
     * @return
     */
    public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
        // Broker 故障延迟机制
        if (this.sendLatencyFaultEnable) {
            try {
                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
                    MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                    // 如果这个 MessageQueue 不在故障期,可用,直接返回
                    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) {
                    // 更新 MessageQueue 信息,返回 MessageQueue
                    final MessageQueue mq = tpInfo.selectOneMessageQueue();
                    if (notBestBroker != null) {
                        mq.setBrokerName(notBestBroker);
                        mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                    }
                    return mq;
                } else {
                    // 如果没有可写的 writeQueueNums,从隔离中移除这个 broker,然后等待这个 broker 有可以写的消息队列
                    latencyFaultTolerance.remove(notBestBroker);
                }
            } catch (Exception e) {
                log.error("Error occurred when selecting message queue", e);
            }
            // 如果不行的话,默认选择一个队列
            return tpInfo.selectOneMessageQueue();
        }
        // 非 Broker 故障延迟机制
        return tpInfo.selectOneMessageQueue(lastBrokerName);
    }

    /**
     * 将此 broker 进行多长时间的隔离,隔离为 true,隔离时间为30s为基础进行隔离时间的计算;否则隔离时间为: 上次发送消息的延迟时长为基础进行隔离时间的计算
     * @param brokerName
     * @param currentLatency
     * @param isolation
     */
    public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
        // 启用故障延迟
        if (this.sendLatencyFaultEnable) {
            // 计算隔离时长
            long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
            this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
        }
    }
    // 计算隔离时长
    private long computeNotAvailableDuration(final long currentLatency) {
        for (int i = latencyMax.length - 1; i >= 0; i--) {
            if (currentLatency >= latencyMax[i])
                return this.notAvailableDuration[i];
        }

        return 0;
    }
}

隔离接口
public interface LatencyFaultTolerance<T> {
    /**
     * 将此 broker 进行 notAvailableDuration 时间的隔离, 这段时间内 broker 为不可用
     * @param name brokerName
     * @param currentLatency 消息发送故障延迟时间
     * @param notAvailableDuration 不可用持续时长,在这个时间内,Broker 将被规避
     */
    void updateFaultItem(final T name, final long currentLatency, final long notAvailableDuration);

    /**
     * 判断 Broker 是否可用,不在进行隔离
     * @param name
     * @return
     */
    boolean isAvailable(final T name);

    /**
     * 移除已经被隔离的 broker,意味着 Broker 重新参与路由计算

     * @param name
     */
    void remove(final T name);

    /**
     * 尝试从规避的 Broker 中选择一个可用 Broker,如果没有找到,将返回 null
     * @return
     */
    T pickOneAtLeast();
}
隔离接口实现

package org.apache.rocketmq.client.latency;

import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.rocketmq.client.common.ThreadLocalIndex;

public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String> {
    // 隔离 Broker 的存储集合
    private final ConcurrentHashMap<String, FaultItem> faultItemTable = new ConcurrentHashMap<String, FaultItem>(16);

    private final ThreadLocalIndex whichItemWorst = new ThreadLocalIndex();

    /**
     * 将此 broker 进行 notAvailableDuration 时间的隔离, 这段时间内 broker 为不可用
     * @param name brokerName
     * @param currentLatency 消息发送故障延迟时间
     * @param notAvailableDuration 不可用持续时长,在这个时间内,Broker 将被规避
     */
    @Override
    public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
        // 判断这个 broker 是否已经被隔离了
        FaultItem old = this.faultItemTable.get(name);
        // 没有隔离,进行隔离
        if (null == old) {
            final FaultItem faultItem = new FaultItem(name);
            faultItem.setCurrentLatency(currentLatency);
            faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
            //
            old = this.faultItemTable.putIfAbsent(name, faultItem);
            if (old != null) {
                old.setCurrentLatency(currentLatency);
                old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
            }
        //  更新 broker 不被隔离的时间点
        } else {
            old.setCurrentLatency(currentLatency);
            old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
        }
    }
    // 获取一个不被隔离的 broker 机器
    @Override
    public boolean isAvailable(final String name) {
        final FaultItem faultItem = this.faultItemTable.get(name);
        if (faultItem != null) {
            return faultItem.isAvailable();
        }
        return true;
    }
    // 移除一个被隔离的 broker
    @Override
    public void remove(final String name) {
        this.faultItemTable.remove(name);
    }
    // 随机选择一个 Broker
    @Override
    public String pickOneAtLeast() {
        final Enumeration<FaultItem> elements = this.faultItemTable.elements();
        List<FaultItem> tmpList = new LinkedList<FaultItem>();
        while (elements.hasMoreElements()) {
            final FaultItem faultItem = elements.nextElement();
            tmpList.add(faultItem);
        }

        if (!tmpList.isEmpty()) {
            // 集合洗牌
            Collections.shuffle(tmpList);
            // 集合排序
            Collections.sort(tmpList);

            final int half = tmpList.size() / 2;
            if (half <= 0) {
                return tmpList.get(0).getName();
            } else {
                final int i = this.whichItemWorst.getAndIncrement() % half;
                return tmpList.get(i).getName();
            }
        }

        return null;
    }

    @Override
    public String toString() {
        return "LatencyFaultToleranceImpl{" +
            "faultItemTable=" + faultItemTable +
            ", whichItemWorst=" + whichItemWorst +
            '}';
    }

    /**
     * 失败条目(规避规则条目)
     */
    class FaultItem implements Comparable<FaultItem> {
        // 条目唯一键,这里为 brokerName
        private final String name;
        // 本次消息发送延迟
        private volatile long currentLatency;
        // 此 broker 变为不在被隔离的开始时间点
        private volatile long startTimestamp;

        public FaultItem(final String name) {
            this.name = name;
        }

        @Override
        public int compareTo(final FaultItem other) {
            if (this.isAvailable() != other.isAvailable()) {
                if (this.isAvailable())
                    return -1;

                if (other.isAvailable())
                    return 1;
            }

            if (this.currentLatency < other.currentLatency)
                return -1;
            else if (this.currentLatency > other.currentLatency) {
                return 1;
            }

            if (this.startTimestamp < other.startTimestamp)
                return -1;
            else if (this.startTimestamp > other.startTimestamp) {
                return 1;
            }

            return 0;
        }
        // 当前被隔离的 broker 可用,不被隔离了
        public boolean isAvailable() {
            return (System.currentTimeMillis() - startTimestamp) >= 0;
        }

        @Override
        public int hashCode() {
            int result = getName() != null ? getName().hashCode() : 0;
            result = 31 * result + (int) (getCurrentLatency() ^ (getCurrentLatency() >>> 32));
            result = 31 * result + (int) (getStartTimestamp() ^ (getStartTimestamp() >>> 32));
            return result;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o)
                return true;
            if (!(o instanceof FaultItem))
                return false;

            final FaultItem faultItem = (FaultItem) o;

            if (getCurrentLatency() != faultItem.getCurrentLatency())
                return false;
            if (getStartTimestamp() != faultItem.getStartTimestamp())
                return false;
            return getName() != null ? getName().equals(faultItem.getName()) : faultItem.getName() == null;

        }

        @Override
        public String toString() {
            return "FaultItem{" +
                "name='" + name + '\'' +
                ", currentLatency=" + currentLatency +
                ", startTimestamp=" + startTimestamp +
                '}';
        }

        public String getName() {
            return name;
        }

        public long getCurrentLatency() {
            return currentLatency;
        }

        public void setCurrentLatency(final long currentLatency) {
            this.currentLatency = currentLatency;
        }

        public long getStartTimestamp() {
            return startTimestamp;
        }

        public void setStartTimestamp(final long startTimestamp) {
            this.startTimestamp = startTimestamp;
        }

    }
}

消息发送

  • 同步发送:一次发送失败或者抛出异常,就会通过 for 循环,无条件进行循环;
  • 异步发送:一次发送之后,如果 broker 挂了,producer 不能接收到 broker 的应答(失败或者成功),就不会发生消息的重新发送;只有在 producer 收到 broker 应答之后,才会去决定是否发生消息重试,参看 MQClientAPIImpl#sendMessageSync#onExceptionImpl。
  • oneway:只是一次发送,不关心后续结果,不能保证成功。
消息发送的可靠性

NameServer 集群各个机器之间互不通信,Broker 每 30s 向 NameServer 注册一次,不能保证各个 NameServer 中注册的 Broker 路由信息实时的一致,NameServer 集群也没有 Master、Slave 节点区分,不能保证 NameServer 单个机器的高可用,通过多个 NameServer 节点,保证了注册中心的集群高可用,然后NameServer 中的 Broker 的路由信息存在延后性,也不能保证 Producer 发送消息的 Broker 机器实时可用,从而发送消息不一定能发送到 Broker 机器是否成功。然后 Producer 发送消息的高可用,采用的是 Producer 向 Broker 发送消息,Broker 进行消息 Ack 应答,来确定消息发送给了 Broker,Broker 接受到了消息。Producer 高可用还采用了另外两种方法。

  1. Broker 故障的延迟机制,Broker 故障了进行一定时间的隔离,然后保证不再向这个 Broker 发送消息,一定时间后进行隔离结束。
  2. Producer 发送消息的重试机制,一般总共3次。
  3. Producer 发送消息不能保证Exactly once(恰好一次),它只能保证At least once(最少一次),发送消息可能重复问题,需要开发者自己进行处理。
消息发送流程

DefaultMQProducer#send->DefaultMQProducerImpl#send-> DefaultMQProducerImpl#sendDefaultImpl

/**
 * 发送同步消息,存在潜在的消息发送重复问题
 * Send message in synchronous mode. This method returns only when the sending procedure totally completes. </p>
 *
 * <strong>Warn:</strong> this method has internal retry-mechanism, that is, internal implementation will retry
 * {@link #retryTimesWhenSendFailed} times before claiming failure. As a result, multiple messages may potentially
 * delivered to broker(s). It's up to the application developers to resolve potential duplication issue.
 *
 */
@Override
public SendResult send(
    Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    Validators.checkMessage(msg, this);
    //  对 topic 进行包装,包装了需要特殊处理的 topic: "%RETRY%"、"%DLQ%",进行消息的特殊处理,比如重试
    msg.setTopic(withNamespace(msg.getTopic()));
    return this.defaultMQProducerImpl.send(msg);
}

/**
 * 同步、异步、oneway 发送消息,最后会调用者 sendDefaultImpl() 方法进行,消息发送,最后都会调用 sendKernelImpl() 这个核心的发送方法,进行发送的封装和最后的发送。
 * 同步发送:一次发送失败或者抛出异常,就会通过 for 循环,无条件进行循环;
 * 异步发送:一次发送之后,如果 broker 挂了,producer 不能接收到 broker 的应答(失败或者成功),就不会发生消息的重新发送;只有在 producer 收到 broker 应答之后,才会去决定是否发生消息重试,参看 MQClientAPIImpl#sendMessageSync#onExceptionImpl。
 * oneway:只是一次发送,不关心后续结果,不能保证成功
 * @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 {
    // 检测生产者状态
    this.makeSureStateOK();
    // 检测消息
    Validators.checkMessage(msg, this.defaultMQProducer);
    final long invokeID = random.nextLong();
    // 第一次发送时的信息,检测超时使用
    long beginTimestampFirst = System.currentTimeMillis();
    long beginTimestampPrev = beginTimestampFirst;
    long endTimestamp = beginTimestampFirst;
    // 查找 topic 路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    // 存在路由信息,消息队列不为空
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        boolean callTimeout = false;
        MessageQueue mq = null;
        Exception exception = null;
        SendResult sendResult = null;
        // 消息重试次数,同步发送次数一共3次;异步和oneWay 发送次数为 1 次;
        int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
        int times = 0;
        String[] brokersSent = new String[timesTotal];
        // 消息发送失败重试
        for (; times < timesTotal; times++) {
            // 最后一个发送消息的 broker
            String lastBrokerName = null == mq ? null : mq.getBrokerName();
            // 选择 MessageQueue
            MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
            // 选择的消息队列不为 null,进行发送,否则 break,跳出重试的 for 循环
            if (mqSelected != null) {
                mq = mqSelected;
                brokersSent[times] = mq.getBrokerName();
                try {
                    beginTimestampPrev = System.currentTimeMillis();
                    // 如果发生发送重试,重置 message topic 信息
                    if (times > 0) {
                        //Reset topic with namespace during resend.
                        msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
                    }
                    // 超时跳出 for 循环
                    long costTime = beginTimestampPrev - beginTimestampFirst;
                    if (timeout < costTime) {
                        callTimeout = true;
                        break;
                    }
                    // 具体发送逻辑
                    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                    endTimestamp = System.currentTimeMillis();
                    // 如果发送时间比较长,调用 updateFaultItem,将这个 broker 隔离一定时长;根据隔离策略:550毫秒以内,隔离时长为 0;
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                    switch (communicationMode) {
                        // 异步发送,不进行重试,返回为 null
                        case ASYNC:
                            return null;
                        // oneway,不进行重试,返回为 null
                        case ONEWAY:
                            return null;
                        case SYNC:
                            // 如果消息发送失败,发送另一个 broker 且,不等发送消息的存储结果
                            if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                                if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                    continue;
                                }
                            }

                            return sendResult;
                        default:
                            break;
                    }
                    // 如果发送错误是 broker 端发生的,进行 30000 秒为基础的隔离时长计算
                } catch (RemotingException e) {
                    endTimestamp = System.currentTimeMillis();
                    // 发送消息失败,进行以 30000 为基础的隔离时长计算
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                    log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                    log.warn(msg.toString());
                    exception = e;
                    continue;
                } catch (MQClientException e) {
                    endTimestamp = System.currentTimeMillis();
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                    log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                    log.warn(msg.toString());
                    exception = e;
                    continue;
                } catch (MQBrokerException e) {
                    endTimestamp = System.currentTimeMillis();
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                    log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                    log.warn(msg.toString());
                    exception = e;
                    switch (e.getResponseCode()) {
                        case ResponseCode.TOPIC_NOT_EXIST:
                        case ResponseCode.SERVICE_NOT_AVAILABLE:
                        case ResponseCode.SYSTEM_ERROR:
                        case ResponseCode.NO_PERMISSION:
                        case ResponseCode.NO_BUYER_ID:
                        case ResponseCode.NOT_IN_CURRENT_UNIT:
                            continue;
                        //    如果不是上面的发送失败结果,进行发送结果返回
                        default:
                            if (sendResult != null) {
                                return sendResult;
                            }

                            throw e;
                    }
                } catch (InterruptedException e) {
                    endTimestamp = System.currentTimeMillis();
                    // 发送被打断,进行 broker 隔离,隔离以延迟时长计算
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                    log.warn(String.format("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                    log.warn(msg.toString());

                    log.warn("sendKernelImpl exception", e);
                    log.warn(msg.toString());
                    throw e;
                }
            } else {
                break;
            }
        }
        // 重试结束,如果发送结果不为空,进行返回
        if (sendResult != null) {
            return sendResult;
        }

        String info = String.format("Send [%d] times, still failed, cost [%d]ms, Topic: %s, BrokersSent: %s",
            times,
            System.currentTimeMillis() - beginTimestampFirst,
            msg.getTopic(),
            Arrays.toString(brokersSent));

        info += FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED);

        MQClientException mqClientException = new MQClientException(info, exception);
        // 超时抛出异常
        if (callTimeout) {
            throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout");
        }
        // 如果出现错误,进行错误代码封装,并抛出错误
        if (exception instanceof MQBrokerException) {
            mqClientException.setResponseCode(((MQBrokerException) exception).getResponseCode());
        } else if (exception instanceof RemotingConnectException) {
            mqClientException.setResponseCode(ClientErrorCode.CONNECT_BROKER_EXCEPTION);
        } else if (exception instanceof RemotingTimeoutException) {
            mqClientException.setResponseCode(ClientErrorCode.ACCESS_BROKER_TIMEOUT);
        } else if (exception instanceof MQClientException) {
            mqClientException.setResponseCode(ClientErrorCode.BROKER_NOT_EXIST_EXCEPTION);
        }

        throw mqClientException;
    }
    // 不存在路由信息
    // 校验 NameServer 配置
    validateNameServerSetting();
    // 直接抛出没有路由信息的异常信息
    throw new MQClientException("No route info of this topic: " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
        null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION);
}

/**
 * 发送消息的核心方法
 * @param msg
 * @param mq
 * @param communicationMode
 * @param sendCallback
 * @param topicPublishInfo
 * @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 {
    long beginStartTime = System.currentTimeMillis();
    // 查找 broker 地址
    String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
    // Producer 端没有缓存,请求 NameServer 服务器,再次获取 broker 地址
    if (null == brokerAddr) {
        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
            // 如果不是批量消息,设置 messageId
            if (!(msg instanceof MessageBatch)) {
                MessageClientIDSetter.setUniqID(msg);
            }
            // 设置消息 INSTANCE_ID 属性 property
            boolean topicWithNamespace = false;
            if (null != this.mQClientFactory.getClientConfig().getNamespace()) {
                msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace());
                topicWithNamespace = true;
            }
            // 系统标志
            int sysFlag = 0;
            // 是否压缩
            boolean msgBodyCompressed = false;
            if (this.tryToCompressMessage(msg)) {
                // sysFlag |= MessageSysFlag.COMPRESSED_FLAG 是 sysFlag = sysFlag | MessageSysFlag.COMPRESSED_FLAG 是 or,或的逻辑
                sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
                msgBodyCompressed = true;
            }
            // 是否是事务 Prepard 消息,TRAN_MSG
            final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
            if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
                sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
            }
            // 检测消息发送时的禁止钩子
            if (hasCheckForbiddenHook()) {
                CheckForbiddenContext checkForbiddenContext = new CheckForbiddenContext();
                checkForbiddenContext.setNameSrvAddr(this.defaultMQProducer.getNamesrvAddr());
                checkForbiddenContext.setGroup(this.defaultMQProducer.getProducerGroup());
                checkForbiddenContext.setCommunicationMode(communicationMode);
                checkForbiddenContext.setBrokerAddr(brokerAddr);
                checkForbiddenContext.setMessage(msg);
                checkForbiddenContext.setMq(mq);
                checkForbiddenContext.setUnitMode(this.isUnitMode());
                this.executeCheckForbiddenHook(checkForbiddenContext);
            }
            // 如果注册了消息发送钩子函数,则执行消息发送之前的增强逻辑。
            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);
                context.setNamespace(this.defaultMQProducer.getNamespace());
                // 事务消息的准备
                String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (isTrans != null && isTrans.equals("true")) {
                    // 设置消息事务类型,事务一半
                    context.setMsgType(MessageType.Trans_Msg_Half);
                }
                // 设置消息 Delay 类型
                if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
                    context.setMsgType(MessageType.Delay_Msg);
                }
                this.executeSendMessageHookBefore(context);
            }
            // 构建发送消息的请求
            SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
            requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
            requestHeader.setTopic(msg.getTopic());
            requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
            // topicQueue 序列号
            requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
            requestHeader.setQueueId(mq.getQueueId());
            // 消息系统标记
            requestHeader.setSysFlag(sysFlag);
            requestHeader.setBornTimestamp(System.currentTimeMillis());
            // 消息标记,RocketMQ 不做任何处理
            requestHeader.setFlag(msg.getFlag());
            requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
            requestHeader.setReconsumeTimes(0);
            requestHeader.setUnitMode(this.isUnitMode());
            requestHeader.setBatch(msg instanceof MessageBatch);
            // 如果是  "%RETRY%" 前缀的 topic
            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:
                    Message tmpMessage = msg;
                    boolean messageCloned = false;
                    // 是否压缩
                    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);
                    }
                    // 特殊 topic 包装
                    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;
                case ONEWAY:
                case SYNC:
                    long costTimeSync = System.currentTimeMillis() - beginStartTime;
                    if (timeout < costTimeSync) {
                        throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                    }
                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                        brokerAddr,
                        mq.getBrokerName(),
                        msg,
                        requestHeader,
                        timeout - costTimeSync,
                        communicationMode,
                        context,
                        this);
                    break;
                default:
                    assert false;
                    break;
            }
            // 发送消息后的,后置钩子
            if (this.hasSendMessageHook()) {
                context.setSendResult(sendResult);
                this.executeSendMessageHookAfter(context);
            }

            return sendResult;
            // 消息抛出异常之后也会执行消息发送后的钩子
        } catch (RemotingException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } catch (MQBrokerException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } catch (InterruptedException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } finally {
            // 最后还原消息体
            msg.setBody(prevBody);
            // 设置 topic
            msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
        }
    }
    // 抛出 broker 不存在异常
    throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}

/**
 * 同步、oneway、异步具体发送消息请求
 * @param addr
 * @param brokerName
 * @param msg
 * @param requestHeader
 * @param timeoutMillis
 * @param communicationMode
 * @param sendCallback
 * @param topicPublishInfo
 * @param instance
 * @param retryTimesWhenSendFailed
 * @param context
 * @param producer
 * @return
 * @throws RemotingException
 * @throws MQBrokerException
 * @throws InterruptedException
 */
public SendResult sendMessage(
    final String addr,
    final String brokerName,
    final Message msg,
    final SendMessageRequestHeader requestHeader,
    final long timeoutMillis,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final TopicPublishInfo topicPublishInfo,
    final MQClientInstance instance,
    final int retryTimesWhenSendFailed,
    final SendMessageContext context,
    final DefaultMQProducerImpl producer
) throws RemotingException, MQBrokerException, InterruptedException {
    long beginStartTime = System.currentTimeMillis();
    RemotingCommand request = null;
    String msgType = msg.getProperty(MessageConst.PROPERTY_MESSAGE_TYPE);
    boolean isReply = msgType != null && msgType.equals(MixAll.REPLY_MESSAGE_FLAG);
    if (isReply) {
        if (sendSmartMsg) {
            SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE_V2, requestHeaderV2);
        } else {
            request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE, requestHeader);
        }
    } else {
        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;
        case SYNC:
            long costTimeSync = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTimeSync) {
                throw new RemotingTooMuchRequestException("sendMessage call timeout");
            }
            return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request);
        default:
            assert false;
            break;
    }

    return null;
}
同步消息发送
/**
 * 同步发送请求
 * @param addr
 * @param brokerName
 * @param msg
 * @param timeoutMillis
 * @param request
 * @return
 * @throws RemotingException
 * @throws MQBrokerException
 * @throws InterruptedException
 */
private SendResult sendMessageSync(
    final String addr,
    final String brokerName,
    final Message msg,
    final long timeoutMillis,
    final RemotingCommand request
) throws RemotingException, MQBrokerException, InterruptedException {
    RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
    assert response != null;
    return this.processSendResponse(brokerName, msg, response);
}
@Override
public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
    throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
    long beginStartTime = System.currentTimeMillis();
    final Channel channel = this.getAndCreateChannel(addr);
    if (channel != null && channel.isActive()) {
        try {
            doBeforeRpcHooks(addr, request);
            long costTime = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTime) {
                throw new RemotingTimeoutException("invokeSync call timeout");
            }
            RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
            doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
            return response;
        } catch (RemotingSendRequestException e) {
            log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
            this.closeChannel(addr, channel);
            throw e;
        } catch (RemotingTimeoutException e) {
            if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
                this.closeChannel(addr, channel);
                log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
            }
            log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
            throw e;
        }
    } else {
        this.closeChannel(addr, channel);
        throw new RemotingConnectException(addr);
    }
}

private SendResult processSendResponse(
    final String brokerName,
    final Message msg,
    final RemotingCommand response
) throws MQBrokerException, RemotingCommandException {
    SendStatus sendStatus;
    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: {
            throw new MQBrokerException(response.getCode(), response.getRemark());
        }
    }

    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());
    }

    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());
    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;
}

异步消息发送
/**
 * 异步发送请求,异步发送注册回调函数,只有收到 broker 的 response 之后才能进行重试,如果网络异常、网络超时、broker 挂了等因素,没有返回 response,并不会发生发送消息的重试
 * @param addr
 * @param brokerName
 * @param msg
 * @param timeoutMillis
 * @param request
 * @param sendCallback
 * @param topicPublishInfo
 * @param instance
 * @param retryTimesWhenSendFailed
 * @param times
 * @param context
 * @param producer
 * @throws InterruptedException
 * @throws RemotingException
 */
private void sendMessageAsync(
    final String addr,
    final String brokerName,
    final Message msg,
    final long timeoutMillis,
    final RemotingCommand request,
    final SendCallback sendCallback,
    final TopicPublishInfo topicPublishInfo,
    final MQClientInstance instance,
    final int retryTimesWhenSendFailed,
    final AtomicInteger times,
    final SendMessageContext context,
    final DefaultMQProducerImpl producer
) throws InterruptedException, RemotingException {
    final long beginStartTime = System.currentTimeMillis();
    this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
        @Override
        public void operationComplete(ResponseFuture responseFuture) {
            long cost = System.currentTimeMillis() - beginStartTime;
            RemotingCommand response = responseFuture.getResponseCommand();
            if (null == sendCallback && response != null) {

                try {
                    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;
            }

            if (response != null) {
                try {
                    SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
                    assert sendResult != null;
                    if (context != null) {
                        context.setSendResult(sendResult);
                        context.getProducer().executeSendMessageHookAfter(context);
                    }

                    try {
                        sendCallback.onSuccess(sendResult);
                    } catch (Throwable e) {
                    }

                    producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
                } catch (Exception e) {
                    producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true);
                    onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
                        retryTimesWhenSendFailed, times, e, context, false, producer);
                }
            } else {
                producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true);
                if (!responseFuture.isSendRequestOK()) {
                    MQClientException ex = new MQClientException("send request failed", responseFuture.getCause());
                    onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
                        retryTimesWhenSendFailed, times, ex, context, true, producer);
                } else if (responseFuture.isTimeout()) {
                    MQClientException ex = new MQClientException("wait response timeout " + responseFuture.getTimeoutMillis() + "ms",
                        responseFuture.getCause());
                    onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
                        retryTimesWhenSendFailed, times, ex, context, true, producer);
                } else {
                    MQClientException ex = new MQClientException("unknow reseaon", responseFuture.getCause());
                    onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
                        retryTimesWhenSendFailed, times, ex, context, true, producer);
                }
            }
        }
    });
}

/**
 * 异步消息发送异常,进行消息重新发送
 * @param brokerName
 * @param msg
 * @param timeoutMillis
 * @param request
 * @param sendCallback
 * @param topicPublishInfo
 * @param instance
 * @param timesTotal
 * @param curTimes
 * @param e
 * @param context
 * @param needRetry
 * @param producer
 */
private void onExceptionImpl(final String brokerName,
    final Message msg,
    final long timeoutMillis,
    final RemotingCommand request,
    final SendCallback sendCallback,
    final TopicPublishInfo topicPublishInfo,
    final MQClientInstance instance,
    final int timesTotal,
    final AtomicInteger curTimes,
    final Exception e,
    final SendMessageContext context,
    final boolean needRetry,
    final DefaultMQProducerImpl producer
) {
    int tmp = curTimes.incrementAndGet();
    if (needRetry && tmp <= timesTotal) {
        String retryBrokerName = brokerName;//by default, it will send to the same broker
        if (topicPublishInfo != null) { //select one message queue accordingly, in order to determine which broker to send
            MessageQueue mqChosen = producer.selectOneMessageQueue(topicPublishInfo, brokerName);
            retryBrokerName = mqChosen.getBrokerName();
        }
        String addr = instance.findBrokerAddressInPublish(retryBrokerName);
        log.info("async send msg by retry {} times. topic={}, brokerAddr={}, brokerName={}", tmp, msg.getTopic(), addr,
            retryBrokerName);
        try {
            request.setOpaque(RemotingCommand.createNewRequestId());
            sendMessageAsync(addr, retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance,
                timesTotal, curTimes, context, producer);
        } catch (InterruptedException e1) {
            onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1,
                context, false, producer);
        } catch (RemotingConnectException e1) {
            producer.updateFaultItem(brokerName, 3000, true);
            onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1,
                context, true, producer);
        } catch (RemotingTooMuchRequestException e1) {
            onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1,
                context, false, producer);
        } catch (RemotingException e1) {
            producer.updateFaultItem(brokerName, 3000, true);
            onExceptionImpl(retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance, timesTotal, curTimes, e1,
                context, true, producer);
        }
    } else {

        if (context != null) {
            context.setException(e);
            context.getProducer().executeSendMessageHookAfter(context);
        }

        try {
            sendCallback.onException(e);
        } catch (Exception ignored) {
        }
    }
}
OneWay 发送
/**
 * oneway 方式发送同步请求
 * @param addr
 * @param request
 * @param timeoutMillis
 * @throws InterruptedException
 * @throws RemotingConnectException
 * @throws RemotingTooMuchRequestException
 * @throws RemotingTimeoutException
 * @throws RemotingSendRequestException
 */
@Override
public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException,
    RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    final Channel channel = this.getAndCreateChannel(addr);
    if (channel != null && channel.isActive()) {
        try {
            doBeforeRpcHooks(addr, request);
            this.invokeOnewayImpl(channel, request, timeoutMillis);
        } catch (RemotingSendRequestException e) {
            log.warn("invokeOneway: send request exception, so close the channel[{}]", addr);
            this.closeChannel(addr, channel);
            throw e;
        }
    } else {
        this.closeChannel(addr, channel);
        throw new RemotingConnectException(addr);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值