RocketMQ从入门到放弃详解

RocketMQ详解一


  • MQ(message queue)
    消息队列,现在市场上普遍流行大面积都在使用的中间件。


前言

  • Rocketmq,一款分布式队列模型,由阿里研发,参考Kafka并结合阿里实际业务需求的消息中间件。

一、RocketMQ简介

1、优势

RocketMQ既然能留在市面广泛的被使用,那他肯定有很多被大家认可的地方,所以先来说说他的优点

  • 强调集群无单点,可扩展,任意一点高可用,水平可扩展
  • 海量消息堆积能力,消息堆积后,写入低延迟
  • 支持上万个队列(没测试过)
  • 消息失败重试机制
  • 消息可查询
  • 开源社区活跃
  • 另外RocketMQ既然能受住双十一的考验,那么相信它的成熟度也达到了一定的成熟度,哈哈哈哈哈,一句废话。
    但是,RocketMQ和其他MQ产品有同样的问题,用到了他的优点,同样带来了其他的问题,比如增加系统复杂性等的问题,所以这里对他的缺点不在进行描述,有兴趣的可以后面再做了解。

2、RocketMQ角色

本节先对下文中用到的专业名词进行简单的描述
1.Name Server

  • Name Server 为 producer 和 consumer 提供路由信息。

2.Broker

  • Broker 是 RocketMQ 系统的主要角色,其实就是前面一直说的 MQ。Broker接收来自生产者的消息,储存以及为消费者拉取消息的请求做好准备。

3.Topic

  • Topic 是一种消息的逻辑分类,比如说你有订单类的消息,也有库存类的消息,那么就需要进行分类,一个是订单 Topic
    存放订单相关的消息,一个是库存 Topic 存储库存相关的消息。

4.Tag

  • 标签简单的来说就是Topic的细化,一般在业务场景中通过引入标签来标记不同用途的消息。

5.Message

  • Message 是消息的载体。一个 Message 必须指定 topic,相当于寄信的地址。Message 还有一个可选的 tag
    设置,以便消费端可以基于 tag 进行过滤消息。

6.Producer

  • 消息生产者,生产者的作用就是将消息发送到
    MQ,生产者本身既可以产生消息,如读取文本信息等。也可以对外提供接口,由外部应用来调用接口,再由生产者将收到的消息发送到 MQ。

7.Consumer

  • 消息消费者,简单来说,消费 MQ 上的消息的应用程序就是消费者,至于消息是否进行逻辑处理,还是直接存储到数据库等取决于业务需要。

配图更易理解几者的关系

rocketMQ之间的关系

二、执行流程

1、工作流程以及原理

废话不多说,上图
在这里插入图片描述
做图功底不行,凑合看吧,将就一下~
下面我们逐个讲解:

1.Producer

  • Producer既然做为生产者,那他的职责肯定就是负责消息的发送,看源码知道发送消息的入口在DefaultMQProducer类的send()方法,DefaultMQProducer提供了很多重载的方法,这些重载方法,内部调用了DefaultMQProducerImpl内的send方法,发送的核心逻辑就在该类的sendDefaultImpl()方法内。

源码:

private SendResult sendDefaultImpl(Message msg, CommunicationMode communicationMode, SendCallback sendCallback, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        this.makeSureStateOK();
        Validators.checkMessage(msg, this.defaultMQProducer);
        long invokeID = this.random.nextLong();
        long beginTimestampFirst = System.currentTimeMillis();
        long beginTimestampPrev = beginTimestampFirst;
        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
        if (topicPublishInfo != null && topicPublishInfo.ok()) {
            boolean callTimeout = false;
            MessageQueue mq = null;
            Exception exception = null;
            SendResult sendResult = null;
            int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
            int times = 0;
            String[] brokersSent = new String[timesTotal];

            while(true) {
                label129: {
                    String info;
                    if (times < timesTotal) {
                        info = null == mq ? null : mq.getBrokerName();
                        MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, info);
                        if (mqSelected != null) {
                            mq = mqSelected;
                            brokersSent[times] = mqSelected.getBrokerName();

                            long endTimestamp;
                            try {
                                beginTimestampPrev = System.currentTimeMillis();
                                if (times > 0) {
                                    msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
                                }

                                long costTime = beginTimestampPrev - beginTimestampFirst;
                                if (timeout >= costTime) {
                                    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                                    endTimestamp = System.currentTimeMillis();
                                    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 || !this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                            return sendResult;
                                        }
                                    default:
                                        break label129;
                                    }
                                }

                                callTimeout = true;
                            } catch (RemotingException var26) {
                                endTimestamp = System.currentTimeMillis();
                                this.updateFaultItem(mqSelected.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                                this.log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mqSelected), var26);
                                this.log.warn(msg.toString());
                                exception = var26;
                                break label129;
                            } catch (MQClientException var27) {
                                endTimestamp = System.currentTimeMillis();
                                this.updateFaultItem(mqSelected.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                                this.log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mqSelected), var27);
                                this.log.warn(msg.toString());
                                exception = var27;
                                break label129;
                            } catch (MQBrokerException var28) {
                                endTimestamp = System.currentTimeMillis();
                                this.updateFaultItem(mqSelected.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                                this.log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mqSelected), var28);
                                this.log.warn(msg.toString());
                                exception = var28;
                                switch(var28.getResponseCode()) {
                                case 1:
                                case 14:
                                case 16:
                                case 17:
                                case 204:
                                case 205:
                                    break label129;
                                default:
                                    if (sendResult != null) {
                                        return sendResult;
                                    } else {
                                        throw var28;
                                    }
                                }
                            } catch (InterruptedException var29) {
                                endTimestamp = System.currentTimeMillis();
                                this.updateFaultItem(mqSelected.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                                this.log.warn(String.format("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mqSelected), var29);
                                this.log.warn(msg.toString());
                                this.log.warn("sendKernelImpl exception", var29);
                                this.log.warn(msg.toString());
                                throw var29;
                            }
                        }
                    }

                    if (sendResult != null) {
                        return sendResult;
                    }

                    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 = info + FAQUrl.suggestTodo("http://rocketmq.apache.org/docs/faq/");
                    MQClientException mqClientException = new MQClientException(info, (Throwable)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(10001);
                    } else if (exception instanceof RemotingTimeoutException) {
                        mqClientException.setResponseCode(10002);
                    } else if (exception instanceof MQClientException) {
                        mqClientException.setResponseCode(10003);
                    }

                    throw mqClientException;
                }

                ++times;
            }
        } else {
            List<String> nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList();
            if (null != nsList && !nsList.isEmpty()) {
                throw (new MQClientException("No route info of this topic, " + msg.getTopic() + FAQUrl.suggestTodo("http://rocketmq.apache.org/docs/faq/"), (Throwable)null)).setResponseCode(10005);
            } else {
                throw (new MQClientException("No name server address, please set it." + FAQUrl.suggestTodo("http://rocketmq.apache.org/docs/faq/"), (Throwable)null)).setResponseCode(10004);
            }
        }
    }
  • 根据源码可以看到先对生产者和消息进行判断
        this.makeSureStateOK();
        Validators.checkMessage(msg, this.defaultMQProducer);
  • 之后调用了DefaultMQProducerImpl.tryToFindTopicPublishInfo()方法
private TopicPublishInfo tryToFindTopicPublishInfo(String topic) {
        TopicPublishInfo topicPublishInfo = (TopicPublishInfo)this.topicPublishInfoTable.get(topic);
        if (null == topicPublishInfo || !topicPublishInfo.ok()) {
            this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
            topicPublishInfo = (TopicPublishInfo)this.topicPublishInfoTable.get(topic);
        }

        if (!topicPublishInfo.isHaveTopicRouterInfo() && !topicPublishInfo.ok()) {
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
            topicPublishInfo = (TopicPublishInfo)this.topicPublishInfoTable.get(topic);
            return topicPublishInfo;
        } else {
            return topicPublishInfo;
        }
    }
  • 将从NameServer获取到的Topic路由信息(TopicRouteData)封装成TopicPublishInfo,然后根据你传入的指定Topic获取路由信息,如果获取不到,可以看到调用了MQClientInstance.updateTopicRouteInfoFromNameServer()方法来进行再次获取。

源码

public boolean updateTopicRouteInfoFromNameServer(String topic, boolean isDefault, DefaultMQProducer defaultMQProducer) {
        try {
            if (!this.lockNamesrv.tryLock(3000L, TimeUnit.MILLISECONDS)) {
                this.log.warn("updateTopicRouteInfoFromNameServer tryLock timeout {}ms", 3000L);
            } else {
                try {
                    TopicRouteData topicRouteData;
                    if (isDefault && defaultMQProducer != null) {
                        topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(), 3000L);
                        if (topicRouteData != null) {
                            Iterator var5 = topicRouteData.getQueueDatas().iterator();

                            while(var5.hasNext()) {
                                QueueData data = (QueueData)var5.next();
                                int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
                                data.setReadQueueNums(queueNums);
                                data.setWriteQueueNums(queueNums);
                            }
                        }
                    } else {
                        topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 3000L);
                    }

                    if (topicRouteData == null) {
                        this.log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}", topic);
                        return false;
                    }

                    TopicRouteData old = (TopicRouteData)this.topicRouteTable.get(topic);
                    boolean changed = this.topicRouteDataIsChange(old, topicRouteData);
                    if (!changed) {
                        changed = this.isNeedUpdateTopicRouteInfo(topic);
                    } else {
                        this.log.info("the topic[{}] route info changed, old[{}] ,new[{}]", new Object[]{topic, old, topicRouteData});
                    }

                    if (!changed) {
                        return false;
                    }

                    TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData();
                    Iterator var8 = topicRouteData.getBrokerDatas().iterator();

                    while(var8.hasNext()) {
                        BrokerData bd = (BrokerData)var8.next();
                        this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
                    }

                    TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData);
                    publishInfo.setHaveTopicRouterInfo(true);
                    Iterator it = this.producerTable.entrySet().iterator();

                    Entry entry;
                    while(it.hasNext()) {
                        entry = (Entry)it.next();
                        MQProducerInner impl = (MQProducerInner)entry.getValue();
                        if (impl != null) {
                            impl.updateTopicPublishInfo(topic, publishInfo);
                        }
                    }

                    Set<MessageQueue> subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData);
                    it = this.consumerTable.entrySet().iterator();

                    while(it.hasNext()) {
                        entry = (Entry)it.next();
                        MQConsumerInner impl = (MQConsumerInner)entry.getValue();
                        if (impl != null) {
                            impl.updateTopicSubscribeInfo(topic, subscribeInfo);
                        }
                    }

                    this.log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData);
                    this.topicRouteTable.put(topic, cloneTopicRouteData);
                    boolean var24 = true;
                    return var24;
                } catch (Exception var16) {
                    if (!topic.startsWith("%RETRY%") && !topic.equals("TBW102")) {
                        this.log.warn("updateTopicRouteInfoFromNameServer Exception", var16);
                    }

                    return false;
                } finally {
                    this.lockNamesrv.unlock();
                }
            }
        } catch (InterruptedException var18) {
            this.log.warn("updateTopicRouteInfoFromNameServer Exception", var18);
        }

        return false;
    }
  • 该方法是通过MQClientAPIImpl.getTopicRouteInfoFromNameServer()方法获取路由信息。

源码

      //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.rocketmq.client.impl.producer;

import java.util.ArrayList;
import java.util.List;
import org.apache.rocketmq.client.common.ThreadLocalIndex;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.route.QueueData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;

public class TopicPublishInfo {
    private boolean orderTopic = false;
    private boolean haveTopicRouterInfo = false;
    private List<MessageQueue> messageQueueList = new ArrayList();
    private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();
    private TopicRouteData topicRouteData;

    public TopicPublishInfo() {
    }

    public boolean isOrderTopic() {
        return this.orderTopic;
    }

    public void setOrderTopic(boolean orderTopic) {
        this.orderTopic = orderTopic;
    }

    public boolean ok() {
        return null != this.messageQueueList && !this.messageQueueList.isEmpty();
    }

    public List<MessageQueue> getMessageQueueList() {
        return this.messageQueueList;
    }

    public void setMessageQueueList(List<MessageQueue> messageQueueList) {
        this.messageQueueList = messageQueueList;
    }

    public ThreadLocalIndex getSendWhichQueue() {
        return this.sendWhichQueue;
    }

    public void setSendWhichQueue(ThreadLocalIndex sendWhichQueue) {
        this.sendWhichQueue = sendWhichQueue;
    }

    public boolean isHaveTopicRouterInfo() {
        return this.haveTopicRouterInfo;
    }

    public void setHaveTopicRouterInfo(boolean haveTopicRouterInfo) {
        this.haveTopicRouterInfo = haveTopicRouterInfo;
    }

    public MessageQueue selectOneMessageQueue(String lastBrokerName) {
        if (lastBrokerName == null) {
            return this.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 = (MessageQueue)this.messageQueueList.get(pos);
                if (!mq.getBrokerName().equals(lastBrokerName)) {
                    return mq;
                }
            }

            return this.selectOneMessageQueue();
        }
    }

    public MessageQueue selectOneMessageQueue() {
        int index = this.sendWhichQueue.getAndIncrement();
        int pos = Math.abs(index) % this.messageQueueList.size();
        if (pos < 0) {
            pos = 0;
        }

        return (MessageQueue)this.messageQueueList.get(pos);
    }

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

        return -1;
    }

    public String toString() {
        return "TopicPublishInfo [orderTopic=" + this.orderTopic + ", messageQueueList=" + this.messageQueueList + ", sendWhichQueue=" + this.sendWhichQueue + ", haveTopicRouterInfo=" + this.haveTopicRouterInfo + "]";
    }

    public TopicRouteData getTopicRouteData() {
        return this.topicRouteData;
    }

    public void setTopicRouteData(TopicRouteData topicRouteData) {
        this.topicRouteData = topicRouteData;
    }
}
  • 可以看到此时的路由信息TopicPublishInfo中包含的数据,包括队列数据、Broker数据和Broker地址等信息,再次回到源码继续了解

源码

TopicRouteData old = (TopicRouteData)this.topicRouteTable.get(topic);
                    boolean changed = this.topicRouteDataIsChange(old, topicRouteData);
                    if (!changed) {
                        changed = this.isNeedUpdateTopicRouteInfo(topic);
                    } else {
                        this.log.info("the topic[{}] route info changed, old[{}] ,new[{}]", new Object[]{topic, old, topicRouteData});
                    }

                    if (!changed) {
                        return false;
                    }
  • 看到MQClientInstance.updateTopicRouteInfoFromNameServer()方法:判断MQClientInstance.topicRouteTable属性以前缓存的路由信息是否需要更新、DefaultMQProducerImpl.topicPublishInfoTable属性以前的缓路由信息是否需要更新,如果需要则进行更新。此时我们已经选择好了Topic路由,接下来就该选择我们的目标队列了,将消息发送到那个队列中。根据源码我们看到调用了DefaultMQProducerImpl.selectOneMessageQueue()方法,

源码

public MessageQueue selectOneMessageQueue(TopicPublishInfo tpInfo, String lastBrokerName) {
        if (this.sendLatencyFaultEnable) {
            try {
                int index = tpInfo.getSendWhichQueue().getAndIncrement();
                int i = 0;

                while(true) {
                    int writeQueueNums;
                    MessageQueue mq;
                    if (i >= tpInfo.getMessageQueueList().size()) {
                        String notBestBroker = (String)this.latencyFaultTolerance.pickOneAtLeast();
                        writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
                        if (writeQueueNums > 0) {
                            mq = tpInfo.selectOneMessageQueue();
                            if (notBestBroker != null) {
                                mq.setBrokerName(notBestBroker);
                                mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                            }

                            return mq;
                        }

                        this.latencyFaultTolerance.remove(notBestBroker);
                        break;
                    }

                    writeQueueNums = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (writeQueueNums < 0) {
                        writeQueueNums = 0;
                    }

                    mq = (MessageQueue)tpInfo.getMessageQueueList().get(writeQueueNums);
                    if (this.latencyFaultTolerance.isAvailable(mq.getBrokerName()) && (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))) {
                        return mq;
                    }

                    ++i;
                }
            } catch (Exception var7) {
                log.error("Error occurred when selecting message queue", var7);
            }

            return tpInfo.selectOneMessageQueue();
        } else {
            return tpInfo.selectOneMessageQueue(lastBrokerName);
        }
    }
  • 此时就需要分情况讨论了,this.sendLatencyFaultEnable判断,如果开启延迟容错策略,则要考虑Broker的响应时间。

  • 延时容错策略简单的来说,就是选择broker时,如果broker响应时间长,就应该选其他的,如果响应时间达到了一定的标准,就在一段时间内,不去选择则个队列座位目标队列。

接下来分情况讨论:
(1)、开启延时容错策略:

        if (this.sendLatencyFaultEnable) {
            try {
                int index = tpInfo.getSendWhichQueue().getAndIncrement();
                int i = 0;

                while(true) {
                    int writeQueueNums;
                    MessageQueue mq;
                    if (i >= tpInfo.getMessageQueueList().size()) {
                        String notBestBroker = (String)this.latencyFaultTolerance.pickOneAtLeast();
                        writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
                        if (writeQueueNums > 0) {
                            mq = tpInfo.selectOneMessageQueue();
                            if (notBestBroker != null) {
                                mq.setBrokerName(notBestBroker);
                                mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                            }

                            return mq;
                        }

                        this.latencyFaultTolerance.remove(notBestBroker);
                        break;
                    }

                    writeQueueNums = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (writeQueueNums < 0) {
                        writeQueueNums = 0;
                    }

                    mq = (MessageQueue)tpInfo.getMessageQueueList().get(writeQueueNums);
                    if (this.latencyFaultTolerance.isAvailable(mq.getBrokerName()) && (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))) {
                        return mq;
                    }

                    ++i;
                }
            } catch (Exception var7) {
                log.error("Error occurred when selecting message queue", var7);
            }

            return tpInfo.selectOneMessageQueue();
        }
  • 先是遍历了topic路由信息中的所有队列,然后队列对应的Broker状态,如果健康,则直接返回该队列;如果遍历了所有队列后,依然没有找到比较合适的Broker,则从三个方面进行选取,哪个broker更健康则优先,哪个响应时间段优先,哪个不可用时间越小优先。

(2)、关闭延迟容错策略

源码

public MessageQueue selectOneMessageQueue(String lastBrokerName) {
        if (lastBrokerName == null) {
            return this.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 = (MessageQueue)this.messageQueueList.get(pos);
                if (!mq.getBrokerName().equals(lastBrokerName)) {
                    return mq;
                }
            }

            return this.selectOneMessageQueue();
        }
    }
  • 如果没有开启延迟容错策略,就比较简单了,直接从Topic路由信息获取,第一次随机,后续往后选另一个Broker的队列。
    至此,就选择好了目标队列,然后设置一些参数就可以发送你的消息到目标broker上了。
    对于发送消息,有几种类型,简单的做下介绍:

  • 普通消息
    同步发送:Producer发出⼀条消息后,会在收到MQ返回的ACK之后才发下⼀条消息。该方式的消息可靠性最高,但消息发送效率太低。
    异步发送:Producer发出消息后无需等待MQ返回ACK,直接发送下⼀条消息。
    单向发送:Producer仅负责发送消息,不等待、不处理MQ的ACK。该发送方式时MQ也不返回ACK。该方式的消息发送效率最高,但消息可靠性较差。

  • 顺序消息
    顺序消息指的是,严格按照消息的发送顺序进行消费的消息(FIFO)。

  • 延时消息
    当消息写入到Broker后,在指定的时长后才可被消费处理的消息,称为延时消息。

  • 事务消息
    既然牵扯到了事务两个字,那么发送消息事件和其他事件需要同时成功或同时失败。
    具体流程:
    事务消息工作流程

2.Consumer
Consumer消费者,既然是消费者,那么他的职责就是消费消息。想要消费消息,那就要先获取到消息吧,所以我们先来聊聊获取消息的两种模式:
获取消息的类型
(1)、拉取式 pull

  • Consumer主动的从Broker中拉取到消息,Consumer掌握了主动权,一旦获取到了消息,就会启动消费过程,开始消费。

(2)、推送式 push

  • Broker在收到生产者发送的消息后会主动的推送给Consumer,然后进行消息消费。
    Push就是典型的发布-订阅模式,即Consumer向其关联的Queue注册了监听器,一旦发现有新的消息,就会触发回调的执行,这里的回调就是Consumer去Queue中获取消息。但是这种都是基Consumer和Broker的长连接,维护长连接就需要消耗系统资源。
    两种方式的利弊
    pull:
  • 需要实现对关联Queue的遍历,且拉取的时间间隔由用户指定的,所以他的时效性较差;但是便于应用控制消息的拉取。
    push:
  • 封装了对关联Queue的遍历,时效性好;但同样长连接等的维护相对而言更加的耗费资源。
    既然获取到了消息,那么接下来接着聊聊消息消费的两种模式:

(1)、广播模式

  • 广播模式下,相同的Consumer实例都会接收到同一个Topic的全量消息。即每条消息都会被发送到消费者组中的每个Consumer。
    但是这里有一点需要注意,虽然每个Consumer接收到的消息相同,但不代表他们的消费进度一样,比如:队列中有有个消息,有三个Consumer实例,有可能第一个Consumer消费完了,第二个Consumer才消费到第三个消息,所以消费进度由Consumer的消费能力决定,并不是统一的。

(2)、集群模式

  • 集群模式下,相同消费者组中的每个Consumer实例会平均分摊同一个Topic的消息。也就是说每个消息只会被发送到某个Consumer。

这两种消费模式看起来的差别只是一个可以被多个Consumer消费,另一个只被一个Consumer消费吗?

  • 广播模式下,消费进度保存在了Consumer端,因为他们的消费进度不同,所以各自的Consumer保存各自的进度。
  • 集群模式下,消费进度保存在了Broker中,消费者组中的所有Consumer共同消费一个Topic中的消息,同一个消息只会被消费一次,那么他的消费进度会参与到消费的负载均衡中(前面说的平均分摊),所以消费进度是要共享的,所以保存在了Broker中。

此时获取到了消息,进行消费,这块儿源码太多了,就不贴源码了哈。

消息的消费和消息堆积

  • Consumer获取到消息,先将Consumer本地缓存的消息提交到消费线程中,使用业务代码进行消息的消费,说白了你的业务逻辑就是真正的消费过程。刚才我们提到了Consumer的消费能力,那么此时的消费能力就完全的依赖于消费消息的耗时和消费并发度。如果消费过程或者说消费逻辑代码中,业务逻辑很复杂或者bug啥的,耗时较长,那么整体的消息吞吐量肯定很低,至少不会高。所以就会导致Consumer本地的缓存队列达到上限,停止获取消息。最终导致了消息的堆积。
    避免消息堆积
    刚才讲了什么场景会导致消息的堆积,主要有消费耗时和消息消费的并发度,那么接下来就这两点聊聊如何避免消息的堆积。
    (1)、消费耗时
    消费耗时,说白了就是你的消费业务逻辑代码的耗时,可以分为两种情况来讨论:1.CPU耗时。2。IO的耗时。
    所以我们主要关注下面几点
  • 消费消息的逻辑计算和执行复杂度是否过高,代码是否存在可避免的循环等等;
  • 消费逻辑中的I/O操作是否是必须的,能否用本地缓存啥的来代替;
  • 消费逻辑中的步骤能否做异步处理,比如发邮件,在合理的情况下可以做异步处理,由另外的线程去处理,从而避免耗时。
    (2)、消费并发度
    并发,对于这两个字眼并不陌生,所以对于消息并发度一下几个建议去优化:
  • 逐步调大单个Consumer的线程数,观察检测数据,得到最优的线程数和吞吐量;
  • 根据上下游系统的流量峰值来估算出需要的节点数。

3.消息
既然消息要被Consumer消费,那消息也会分为多个种类来被消费,以便适用于不同的场景。
1、普通消息
2、顺序消息

  • 顺序消息就是严格按照消息的发送顺序来进行消费的消息。消费消息时从多个Queue获取消息,这种情况无法保证消息发送和消费的顺序。但是如何将消息仅发送到同一个Queue,就严格保证了消息的顺序性。比如网购订单的下单->支付->发货等流程,就能用到该类消息。

3、延时消息
延时消息,就是消息到达Broker中后,在指定的时间后才可能被消费的消息。比如:电商采购中超时没有进行支付的场景;12306订票超时未支付等等都能用到。

重试机制

  • 消息被消费,如果消费失败,可以根据状态和返回值进入重试队列。可以设置重试的间隔时间和重试次数,来进行消息的重试消费。如果超过了重试的次数,就会进入死信队列。死信队列即达到最大消费次数后,仍然消费失败,此时就说明消费者在正常情况下,是无法消费掉这条消息,这时,并不会将消息丢弃掉,而是将该消息放入一个特殊的队列中,这就是死信队列。
  • 前面说了消费这如果消费消息失败,可以根据状态和返回值来判断是否需要进入重试队列,下面就说下重试机制的实现。
    (1)、return null;
    (2)、throw new RuntimeException(“消费异常了——喂”);
    (3)、return ConsumeConcurrentlyStatus.RECONSUME_LATER;

好了,此篇文章对于大致的RocketMQ角色讲解就到此为止了,对于RocketMQ的配置和其他的一些常见的问题将在不久后和大家见面,return “拜拜”;


总结

  • 作为技术型文章,没有华丽的语言和生动的文字,有的仅仅是数行代码和对其枯燥的描述,希望本文能对即将开始写作的你,有所帮助。

  • 本文为作者原创文章,未经原创作者同意不得转载

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值