消息中间件RocketMQ的简单消息(同步、异步和单向)发送与消费

1. 准备工作

导入MQ客户端依赖

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.4.0</version>
</dependency>

2. 发送和消费的步骤简述

  • 消息发送者步骤简述
1.创建消息生产者producer,并制定生产者组名
2.指定Nameserver地址
3.启动producer
4.创建消息对象 Message,指定主题Topic、Tag和消息体
5.发送消息
6.关闭生产者producer
  • 消息消费者步骤简述
1.创建消费者Consumer,制定消费者组名
2.指定Nameserver地址
3.订阅主题Topic和Tag
4.设置回调函数,处理消息
5.启动消费者consumer

注意:消费者的 Topic 和 Tag 需要和生产者保持一致

3. 发送和消费同步消息

同步消息是最基础也是最简单的一个样例。这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。

3.1 消息发送者

public static void main(String[] args) throws Exception {
        // 实例化消息生产者 Producer
        DefaultMQProducer producer = new DefaultMQProducer("defaultGroup");
        // 设置 NameServer 的地址
        producer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 启动 Producer 实例
        producer.start();
        // 发送5条消息
        for (int i = 0; i < 5; i++) {
            // 创建消息,并指定Topic,Tag和消息体并设置默认编码 UTF-8
            Message message = new Message("TopicA", "TagA", ("Hello RocketMQ"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 发送消息到一个Broker
            SendResult sendResult = producer.send(message);
            // 通过sendResult返回消息是否成功送达
            System.out.println(sendResult);
        }

        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }

执行并打印结果

SendResult [sendStatus=SEND_OK, msgId=0A4A2816026C18B4AAC22CEE54360000, offsetMsgId=0A4A280C00002A9F0000000000005991, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=1], queueOffset=28]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816026C18B4AAC22CEE54410001, offsetMsgId=0A4A280C00002A9F0000000000005A58, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=2], queueOffset=30]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816026C18B4AAC22CEE54430002, offsetMsgId=0A4A280C00002A9F0000000000005B1F, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=3], queueOffset=28]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816026C18B4AAC22CEE54460003, offsetMsgId=0A4A283300002A9F0000000000005FCB, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-b, queueId=0], queueOffset=30]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816026C18B4AAC22CEE544A0004, offsetMsgId=0A4A283300002A9F0000000000006092, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-b, queueId=1], queueOffset=30]

3.2 消息消费者

public static void main(String[] args) throws MQClientException {
        // 实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("defaultGroup");
        // 指定NameServer地址信息
        consumer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 订阅Topic,需要和发送者对上
        consumer.subscribe("TopicA", "TagA");
        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println("Message Received: " + new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.println("消费者启动");
    }

执行并打印结果

消费者启动
Message Received: Hello RocketMQ0
Message Received: Hello RocketMQ1
Message Received: Hello RocketMQ2
Message Received: Hello RocketMQ3
Message Received: Hello RocketMQ4

4. 发送和消费异步消息

异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待 Broker 的响应。

4.1 消息发送者

public static void main(String[] args) throws Exception {
        // 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("defaultGroup");
        // 设置NameServer的地址
        producer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 启动Producer实例
        producer.start();

        for (int i = 0; i < 5; i++) {
            // 创建消息,并指定Topic,Tag和消息体并设置默认编码UTF-8
            Message message = new Message("TopicA", "TagA", ("Hello RocketMQ"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 发送消息到一个Broker,异步发送没有返回值,需要使用 SendCallback 接收异步返回结果的回调
            producer.send(message, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println("发送成功:"+sendResult);
                }

                @Override
                public void onException(Throwable throwable) {
                    System.out.println("发送异常:"+ throwable.getMessage());
                }
            });
        }
        Thread.sleep(1000);
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }

执行并打印结果

发送成功:SendResult [sendStatus=SEND_OK, msgId=0A4A28163B3018B4AAC22CF96CF90000, offsetMsgId=0A4A283300002A9F00000000000063AE, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-b, queueId=2], queueOffset=35]
发送成功:SendResult [sendStatus=SEND_OK, msgId=0A4A28163B3018B4AAC22CF96CF90003, offsetMsgId=0A4A280C00002A9F0000000000005D74, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=2], queueOffset=31]
发送成功:SendResult [sendStatus=SEND_OK, msgId=0A4A28163B3018B4AAC22CF96CF90001, offsetMsgId=0A4A283300002A9F0000000000006475, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-b, queueId=0], queueOffset=31]
发送成功:SendResult [sendStatus=SEND_OK, msgId=0A4A28163B3018B4AAC22CF96CF90002, offsetMsgId=0A4A280C00002A9F0000000000005E3B, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=0], queueOffset=30]
发送成功:SendResult [sendStatus=SEND_OK, msgId=0A4A28163B3018B4AAC22CF96D120004, offsetMsgId=0A4A280C00002A9F0000000000005F02, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=3], queueOffset=29]

特别注意:在异步消息的发送者执行 shutdown 操作之前,一定记得要休眠一下,否则就会出现如下错误

java.lang.IllegalStateException: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [NameServer的IP:9876] failed
at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:681)
at org.apache.rocketmq.client.impl.factory.MQClientInstance.updateTopicRouteInfoFromNameServer(MQClientInstance.java:511)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.tryToFindTopicPublishInfo(DefaultMQProducerImpl.java:692)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendDefaultImpl(DefaultMQProducerImpl.java:556)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.access$300(DefaultMQProducerImpl.java:97)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl$4.run(DefaultMQProducerImpl.java:510)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

我猜测原因是由于是异步发送,如果不休眠的话,消息还没发送出去就被 shutdown 了,这和堆栈信息也符合。如果性能在极差的情况下,像我这样休眠 1 秒很有可能是不够的。

4.2 消息消费者

public static void main(String[] args) throws MQClientException {
        // 实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("defaultGroup");
        // 指定NameServer地址信息
        consumer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 订阅Topic,需要和发送者对上
        consumer.subscribe("TopicA", "TagA");
        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println("Message Received: " + new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.println("消费者启动");
    }

执行并打印结果

消费者启动
Message Received: Hello RocketMQ0
Message Received: Hello RocketMQ3
Message Received: Hello RocketMQ4
Message Received: Hello RocketMQ1
Message Received: Hello RocketMQ2

从消费者代码中可以看出:异步消费者的实现方式和同步消费者实现方式并无区别。
从消费者的结果来看:可以看出异步消费者接收的结果是无序的。

5. 发送和消费单向消息

这种方式主要用在不特别关心发送结果的场景,例如日志发送。

5.1 消息发送者

public static void main(String[] args) throws Exception {
        // 实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("defaultGroup");
        // 设置NameServer的地址
        producer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 启动Producer实例
        producer.start();
        for (int i = 0; i < 5; i++) {
            // 创建消息,并指定Topic,Tag和消息体并设置默认编码UTF-8
            Message message = new Message("TopicA", "TagA", ("Hello RocketMQ"+i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            // 发送单向消息,没有任何返回结果
            producer.sendOneway(message);
        }

        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }

5.2 消息消费者

消息的消费者还是选择同步消息消费者的实现方式,现原封不动的 Cpoy 过来。

public static void main(String[] args) throws MQClientException {
        // 实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("defaultGroup");
        // 指定NameServer地址信息
        consumer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 订阅Topic,需要和发送者对上
        consumer.subscribe("TopicA", "TagA");
        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println("Message Received: " + new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.println("消费者启动");
    }

执行并打印结果

消费者启动
Message Received: Hello RocketMQ2
Message Received: Hello RocketMQ1
Message Received: Hello RocketMQ4
Message Received: Hello RocketMQ0
Message Received: Hello RocketMQ3

从消费结果来看,消费的顺序是无序的

6. 总结

  1. 同步消息、异步消息和单向消息的消费者实现方式是一样的。
  2. 同步消息、异步消息和单向消息的区别在于消息的发送方。
  3. 异步消息发送者没有返回值,需要使用 SendCallback 接收异步返回结果的回调。
  4. 异步消息发送者,在关闭实例之前,建议进行休眠。
  5. 单向消息也是没有返回值的,并且它的消费者也是无序消费。
  6. 单向消息和异步消息的区别是单向消息不需要 SendCallback 来接收异步返回结果的回调。

7. 消费者的消费模式

消费者的消费模式分为两种

  1. 负载均衡模式:消费者采用负载均衡方式消费消息,多个消费者共同消费队列消息,每个消费者处理的消息不同
  2. 广播模式:消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的

7.1 负载均衡模式(默认)

我们使用最基础的同步消息的进行测试,我们先启动两个消费者(多次启动 Main 函数的方法在文章末尾有教程):

public static void main(String[] args) throws MQClientException {
        // 实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("defaultGroup");
        // 指定NameServer地址信息
        consumer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 订阅Topic,需要和发送者对上
        consumer.subscribe("TopicA", "TagA");
        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println("Message Received: " + new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.println("消费者启动");
    }

在这里插入图片描述
然后,执行同步消息发送者发送五条消息(就不贴代码了,代码和上面一样):

SendResult [sendStatus=SEND_OK, msgId=0A4A2816309418B4AAC22D126C540000, offsetMsgId=0A4A283300002A9F0000000000006C3B, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-b, queueId=3], queueOffset=32]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816309418B4AAC22D126CA20001, offsetMsgId=0A4A280C00002A9F0000000000006473, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=0], queueOffset=33]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816309418B4AAC22D126CC40002, offsetMsgId=0A4A280C00002A9F000000000000653A, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=1], queueOffset=32]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816309418B4AAC22D126CC80003, offsetMsgId=0A4A280C00002A9F0000000000006601, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=2], queueOffset=33]
SendResult [sendStatus=SEND_OK, msgId=0A4A2816309418B4AAC22D126CD60004, offsetMsgId=0A4A280C00002A9F00000000000066C8, messageQueue=MessageQueue [topic=TopicA, brokerName=broker-a, queueId=3], queueOffset=31]

最后,我们再来看看两个消费者消费的消息状况:

  • 消费者-1
    在这里插入图片描述
  • 消费者-2
    在这里插入图片描述
    从结果可以看出,两个消费者共同消费队列消息,每个消费者处理的消息不同,它符合上述负载均衡模式。

7.2 广播模式

广播模式的消息发送者不变,这里同样使用同步消息的发送者。而广播模式的消费者需要在同步消费者上加上一句话:

// 设置为广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);

完整代码如下:

public static void main(String[] args) throws MQClientException {
        // 实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("defaultGroup");
        // 指定NameServer地址信息
        consumer.setNamesrvAddr("任何一个NameServer的IP:9876");
        // 订阅Topic,需要和发送者对上
        consumer.subscribe("TopicA", "TagA");

        // 设置为广播模式
        consumer.setMessageModel(MessageModel.BROADCASTING);

        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println("Message Received: " + new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消息者
        consumer.start();
        System.out.println("消费者启动");
    }

我们还是按照上面的步骤,启动两个消费者,然后发送5条消息。最后来看看结果。

  • 消费者-1
    在这里插入图片描述
  • 消费者-2
    在这里插入图片描述
    从结果可以看出,两个消费者共同消费队列消息,每个消费者消费的消息都是相同的,它符合上述广播模式模式。

8. 消费模式总结

  1. 消费模式默认为负载均衡模式
  2. 修改消费模式的方法是: consumer.setMessageModel();
  3. 设置为广播模式:consumer.setMessageModel(MessageModel.BROADCASTING);
  4. 设置负载均衡模式:consumer.setMessageModel(MessageModel.CLUSTERING);

9. 拓展 - 关于如何启动 main 方法多次

  • 首先在右上角启动按钮旁边点击:Edit Configurations
    在这里插入图片描述
  • 然后选择需要多次启动 Main 方法的类,在窗口右上角勾选:Allow parallel run
    在这里插入图片描述

技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值