【消息队列RocketMQ】二、RocketMQ 消息发送与消费:原理与实践

一、RocketMQ 消息发送原理与模式​

1.1 消息发送原理​

RocketMQ 消息发送的核心流程围绕 Producer、NameServer 和 Broker 展开。Producer 启动时,会向 NameServer 请求获取 Topic 的路由信息,这些信息包括 Topic 对应的 Broker 列表以及 Broker 上的队列分布情况。Producer 拿到路由信息后,会根据特定的负载均衡策略(如轮询、随机等),选择合适的 Broker 和队列进行消息发送 。消息发送到 Broker 后,Broker 会将消息存储到 CommitLog 文件中,并更新相关的索引文件(如 ConsumeQueue 和 IndexFile),以便后续 Consumer 能够高效地查询和消费消息。​

1.2 消息发送模式​

1.2.1 同步发送​

同步发送是指 Producer 发送消息后,会阻塞等待 Broker 的响应,直到收到 Broker 返回的确认信息,才会继续执行后续的业务逻辑。这种模式保证了消息发送的可靠性,适用于对消息可靠性要求较高且允许一定等待时间的场景,如订单处理、金融交易等。​

在 CentOS 7 环境下,使用 RocketMQ 自带的示例代码演示同步发送消息:​

  1. 准备工作:确保 RocketMQ 已在 CentOS 7 上正确安装并启动 NameServer 和 Broker。进入 RocketMQ 安装目录的bin文件夹。​
  2. 修改配置:在实际项目中,若需修改 Producer 的相关配置,可通过代码中的配置项实现。例如,设置 NameServer 地址,在 Java 代码中可使用以下方式:
DefaultMQProducer producer = new 
DefaultMQProducer("producer_group_name");
producer.setNamesrvAddr("localhost:9876");

这里producer_group_name为 Producer 组名称,localhost:9876为 NameServer 的地址和端口。​

     3. 发送消息代码:执行以下命令发送同步消息:

sh tools.sh org.apache.rocketmq.example.quickstart.Producer

其底层 Java 代码实现如下:

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

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

        for (int i = 0; i < 10; i++) {
            try {
                Message msg = new Message("TopicTest",
                        "TagA",
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }
}

在上述代码中,创建了DefaultMQProducer实例,设置 NameServer 地址并启动。通过循环发送 10 条消息到名为TopicTest的 Topic 中,TagA为消息标签,消息内容为自定义字符串。发送成功后,打印SendResult信息,包含消息发送的状态、队列偏移量等。​

1.2.2 异步发送​

异步发送中,Producer 发送消息后不会阻塞等待,而是继续执行后续代码,通过回调函数来处理 Broker 返回的响应结果。这种模式适用于对响应时间要求较高,且可以容忍一定消息丢失风险的场景,如日志记录、实时数据分析等。​

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

public class AsyncProducer {
    public static void main(String[] args) throws MQClientException {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group_name");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            Message msg = new Message("TopicTest",
                    "TagA",
                    ("Hello Async RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            producer.send(msg, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.printf("%-10d OK %s%n", index, sendResult.getMsgId());
                }

                @Override
                public void onException(Throwable e) {
                    System.out.printf("%-10d Exception %s%n", index, e);
                    e.printStackTrace();
                }
            });
        }
        producer.shutdown();
    }
}

在该代码里,producer.send方法传入了SendCallback接口的实现类,当消息发送成功时,会在onSuccess方法中打印消息发送成功的信息;若发送失败,onException方法会捕获异常并打印相关信息。​

1.2.3 单向发送​

单向发送即 Producer 只负责将消息发送出去,不关心 Broker 的响应结果,也不会阻塞等待。这种模式具有最高的发送效率,但消息可靠性较低,适用于对消息可靠性要求不高的场景,如即时通讯中的消息推送。​

代码示例如下:

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

public class OnewayProducer {
    public static void main(String[] args) throws MQClientException {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group_name");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 10; i++) {
            Message msg = new Message("TopicTest",
                    "TagA",
                    ("Hello Oneway RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            producer.sendOneway(msg);
        }
        producer.shutdown();
    }
}

sendOneway方法直接将消息发送出去,无返回值和回调处理。​

二、RocketMQ 消息消费原理与模式​

2.1 消息消费原理​

Consumer 启动后,同样会向 NameServer 获取 Topic 的路由信息。根据消费模式(集群消费或广播消费)和负载均衡策略,Consumer 会从对应的 Broker 拉取消息。在拉取消息时,Consumer 会根据本地记录的消费进度(偏移量)从 Broker 的 ConsumeQueue 中获取消息。获取到消息后,Consumer 对消息进行业务处理,处理完成后,会更新本地的消费进度,以便下次继续从正确的位置拉取消息 。​

2.2 消息消费模式​

2.2.1 集群消费​

集群消费模式下,多个 Consumer 实例组成一个消费组,共同消费 Topic 中的消息。每个消息只会被消费组中的一个 Consumer 实例消费,这种模式可以提高消息的消费效率,适用于大部分业务场景。​

在 CentOS 7 上,使用以下命令运行集群消费示例:

sh tools.sh org.apache.rocketmq.example.quickstart.Consumer

Java 代码实现如下:

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class ClusterConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group_name");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

在上述代码中,创建了DefaultMQPushConsumer实例,设置 NameServer 地址和消费起始位置(从第一条消息开始消费),订阅TopicTest主题下的所有消息。通过registerMessageListener注册消息监听器,当接收到消息时,在consumeMessage方法中打印消息内容,并返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS表示消费成功。​

2.2.2 广播消费​

广播消费模式下,Topic 中的消息会被消费组中的所有 Consumer 实例消费。这种模式适用于需要所有消费者都处理相同消息的场景,如系统配置更新通知。​

代码示例如下:

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class BroadcastConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group_name");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.setMessageModel(org.apache.rocketmq.common.message.MessageModel.BROADCASTING);
        consumer.subscribe("TopicTest", "*");

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

与集群消费代码的主要区别在于,通过setMessageModel方法将消费模式设置为BROADCASTING,表示广播消费。​

三、常见问题与解决​

3.1 消息发送失败​

1、原因分析:可能是 NameServer 地址配置错误、Broker 未正常启动、网络连接问题,或者消息大小超过 Broker 配置的限制等。​

2、解决方法:检查 NameServer 地址和端口是否正确;通过日志查看 Broker 的启动状态;检查网络连接是否正常;若消息过大,可调整 Broker 的maxMessageSize配置项,在broker.conf文件中添加或修改:

maxMessageSize=65536

这里将消息最大大小设置为 64KB。

3.2 消息消费异常​

1、原因分析:可能是消费逻辑代码出现异常、消息格式错误、消费进度记录异常等。​

2、解决方法:通过查看 Consumer 的日志,定位消费逻辑中的异常代码;检查消息格式是否符合预期;若消费进度异常,可手动调整消费偏移量,在代码中通过DefaultMQPushConsumer的相关方法实现,如:

consumer.resetOffset("TopicTest", queueId, offset);

其中TopicTest为主题名,queueId为队列编号,offset为指定的偏移量。

你可能感兴趣的:(云原生中间件,rocketmq,消息队列,架构,云计算,中间件)