深入理解RocketMq普通消息和顺序消息使用,原理,优化

1. 背景 最近一直再做一些系统上的压测,并对一些问题做了优化,从这些里面收获了一些很多好的优化经验,后续的文章都会以这方面为主。这次打压的过程中收获比较的大的是,对Ro...
摘要由CSDN通过智能技术生成

1. 背景

最近一直再做一些系统上的压测,并对一些问题做了优化,从这些里面收获了一些很多好的优化经验,后续的文章都会以这方面为主。

这次打压的过程中收获比较的大的是,对RocketMq的一些优化。最开始我们公司使用的是RabbitMq,在一些流量高峰的场景下,发现队列堆积比较严重,导致RabbitMq挂了。为了应对这个场景,最终我们引入了阿里云的RocketMq,RocketMq可以处理可以处理很多消息堆积,并且服务的稳定不挂也可以由阿里云保证。引入了RocketMq了之后,的确解决了队列堆积导致消息队列宕机的问题。

本来以为使用了RocketMq之后,可以万事无忧,但是其实在打压过程中发现了不少问题,这里先提几个问题,大家带着这几个问题在文中去寻找答案:

  1. 在RocketMq中,如果消息队列发生堆积,consumer会发生什么样的影响?

  2. 在RocketMq中,普通消息和顺序消息有没有什么办法提升消息消费速度?

  3. 消息失败重试次数怎么设置较为合理?顺序消息和普通消息有不同吗?

2. 普通消息 VS 顺序消息

在RocketMq中提供了多种消息类型让我们进行配置:

  • 普通消息:没有特殊功能的消息。

  • 分区顺序消息:以分区纬度保持顺序进行消费的消息。

  • 全局顺序消息:全局顺序消息可以看作是只分一个区,始终在同一个分区上进行消费。

  • 定时/延时消息:消息可以延迟一段特定时间进行消费。

  • 事务消息:二阶段事务消息,先进行prepare投递消息,此时不能进行消息消费,当二阶段发出commit或者rollback的时候才会进行消息的消费或者回滚。

虽然配置种类比较繁多,但是使用的还是普通消息和分区顺序消息。后续主要讲的也是这两种消息。

2.1 发送消息

2.1.1 普通消息

普通消息的发送的代码比较简单,如下所示:

    public static void main(String[] args) throws MQClientException, InterruptedException {
    
        DefaultMQProducer producer = new DefaultMQProducer("test_group_producer");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();

        Message msg =
                new Message("Test_Topic", "test_tag", ("Hello World").getBytes(RemotingHelper.DEFAULT_CHARSET));
        SendResult sendResult = producer.send(msg);
        System.out.printf("%s%n", sendResult);
        producer.shutdown();
    }

其内部核心代码为:

    private SendResult sendDefaultImpl(Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        // 1. 根据 topic找到publishInfo
        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
        if (topicPublishInfo != null && topicPublishInfo.ok()) {
            boolean callTimeout = false;
            MessageQueue mq = null;
            Exception exception = null;
            SendResult sendResult = null;
            // 如果是同步 就三次 否则就1次
            int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
            int times = 0;
            String[] brokersSent = new String[timesTotal];
            // 循环
            for (; times < timesTotal; times++) {
                String lastBrokerName = null == mq ? null : mq.getBrokerName();
                MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
                if (mqSelected != null) {
                    mq = mqSelected;
                    brokersSent[times] = mq.getBrokerName();
                    try {
                        beginTimestampPrev = System.currentTimeMillis();
                        if (times > 0) {
                            //Reset topic with namespace during resend.
                            msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
                        }
                        long costTime = beginTimestampPrev - beginTimestampFirst;
                        if (timeout < costTime) {
                            callTimeout = true;
                            break;
                        }

                        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) {
                                    if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                        continue;
                                    }
                                }

                                return sendResult;
                            default:
                                break;
                        }
                    } 
                } else {
                    break;
                }
            }
        // 省略

    }

主要流程如下:

  • Step 1:根据Topic 获取TopicPublishInfo,TopicPublishInfo中有我们的Topic发布消息的信息(),这个数据先从本地获取如果本地没有,则从NameServer去拉取,并且定时每隔20s会去获取TopicPublishInfo。

  • Step 2:获取总共执行次数(用于重试),如果发送方式是同步,那么总共次数会有3次,其他情况只会有1次。

  • Step 3: 从MessageQueue中选取一个进行发送,MessageQueue的概念可以等同于Kafka的partion分区,看作发送消息的最小粒度。这个选择有两种方式:

    • 根据发送延迟进行选择,如果上一次发送的Broker是可用的,则从当前Broker选择遍历循环选择一个,如果不可用那么需要选择一个延迟最低的Broker从当前Broker上选择MessageQueue。

    • 通过轮训的方式进行选择MessageQueue。

  • Step 4: 将Message发送至选择出来的MessageQueue上的Broker。

  • Step 5: 更新Broker的延迟。

  • Step 6: 根据不同的发送方式来处理结果:

    • Async: 异步发送,通过callBack关心结果,所以这里不进行处理。

    • OneWay: 顾名思义,就是单向发送࿰

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值