RocketMQ源码--2--RocketMQ的简单使用

RocketMQ源码–2RocketMQ的简单使用

前言:

使用rocketmq其实非常简单,官方已经给出了各种情况下的简单示例,下面我们就跟随官方的案例来看一下它是如何使用的

官方教程地址

1 依赖

pom中引入这个依赖即可

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

2 发送消息

RocketMQ支持好几种消息发送方式,都有各自的应用场景

  1. 同步发送
  2. 异步发送
  3. 单向发送

2.1 同步发送

同步发送的意思就是说:生产者必须收到rocketmq对该条消息的回复才会继续往下运行,否则就一直阻塞在该行代码那里。

这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。

public class SyncProducer {
    public static void main(String[] args) {
        // rocketmq提供的一个发消息的生产者
        DefaultMQProducer defaultMQProducer = new DefaultMQProducer("sync_test");
        // 必须设置NameServer的地址
        defaultMQProducer.setNamesrvAddr("localhost:9876");
        try {
            // 启动Producer实例
            defaultMQProducer.start();
            for (int i = 0; i < 10; i++) {
                Message message = new Message("test_topic", "tagA", ("第" + i + "条消息").getBytes(RemotingHelper.DEFAULT_CHARSET));
                // 必须要收到回复,否则会一直阻塞
                SendResult sendResult = defaultMQProducer.send(message);
                System.out.printf("%s%n", sendResult);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

NameServer就相当于一个注册中心,所有的broker(真正的mq)都将自己的注册到NameServer上,生产者发送消息的时候,先从NameServer中获取一个broker的地址,然后才将消息发送到这个broker上。

2.1 异步发送

异步发送和同步发送唯一区别就是:同步发送要等发送结果,而异步则不用。

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

public class AsyncProducer {

    public static void main(String[] args) {
        // rocketmq提供的一个发消息的生产者
        DefaultMQProducer defaultMQProducer = new DefaultMQProducer("async_test");
        // 必须设置NameServer的地址
        defaultMQProducer.setNamesrvAddr("localhost:9876");
        try {
            // 启动Producer实例
            defaultMQProducer.start();
            for (int i = 0; i < 10; i++) {
                Message message = new Message("test_topic", "tagA", ("第" + i + "条消息").getBytes(RemotingHelper.DEFAULT_CHARSET));
                // 发完就不管了,直接运行下一行代码
                defaultMQProducer.send(message, new SendCallback() {
                    /**
                     * 收到mq回复发送成功后,回调此方法
                     * @param sendResult
                     */
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        System.out.printf("%s%n", sendResult);
                    }

                    /**
                     * 收到mq回复发送失败后,回调此方法
                     * @param e
                     */
                    @Override
                    public void onException(Throwable e) {
                        System.out.printf("%s%n", e);
                    }
                });

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3 单向发送

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

public class OnewayProducer {
    public static void main(String[] args) {
        // rocketmq提供的一个发消息的生产者
        DefaultMQProducer defaultMQProducer = new DefaultMQProducer("oneway_test");
        // 必须设置NameServer的地址
        defaultMQProducer.setNamesrvAddr("localhost:9876");
        try {
            // 启动Producer实例
            defaultMQProducer.start();
            for (int i = 0; i < 10; i++) {
                Message message = new Message("test_topic", "tagA", ("第" + i + "条消息").getBytes(RemotingHelper.DEFAULT_CHARSET));
                // 发完就不管了,不关心结果,直接运行下一行代码
                defaultMQProducer.sendOneway(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3 消费消息

下面是一个简单的消费者示例,重点地方都已经写好注释

public class Consumer {
    public static void main(String[] args) {

        // 实例化一个消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_test");
        // 设置NameServer的地址
        consumer.setNamesrvAddr("localhost:9876");
        try {
            // 设置消费者订阅的主题topic和tag
            consumer.subscribe("test_topic","tagA");
            // 注册一个监听器来消费消息
            consumer.registerMessageListener(new MessageListenerConcurrently() {
                /**
                 * It is not recommend to throw exception,rather than returning ConsumeConcurrentlyStatus.RECONSUME_LATER if
                 * consumption failure
                 *
                 * @param msgs    msgs.size() >= 1<br> DefaultMQPushConsumer.consumeMessageBatchMaxSize=1,you can modify here
                 * @param context
                 * @return The consume status
                 */
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                    ListIterator<MessageExt> iterator = msgs.listIterator();
                    while (iterator.hasNext()){
                        MessageExt messageExt = iterator.next();
                        try {
                            System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(messageExt.getBody(),"utf-8"));
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                    // 向mq返回一个消费成功的标识
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
            // 启动消费者
            consumer.start();
            System.out.printf("Consumer Started.%n");
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
}

4 顺序消息

什么是顺序消息:就是按照发送消息的顺序来进行消费。比如说,生产者先后发送了3条消息ABC,那么消费者消费的时候也需要按照这个顺序先消费A,再消费B,最后消费C。那么如何做到呢?

首先你得明白rocketmq收发消息的原理:

其实broker收到消息后,会将消息分配到它下面的MessageQueue中,brokerMessageQueue的关系是一对多,即一个broker下面可能会存在多个MessageQueue。默认情况下,一个broker收到的消息会被均匀的存储到它下面的MessageQueue上,MessageQueue就是我们常说的消息队列,rocketmq收到的所有消息都存到这些消息队列中。

消息存储到MessageQueue上后,消费者又是如何消费的呢?

和生产者一样,消费这也是先从NameServer中获取一个broker的地址,连上broker后,均匀的从它下面的MessageQueue中获取消息,消费

整个发送和消费的过程清楚了,那么你就应该很容易想到上面这种情况如何实现了

是不是只要我们将3条消息ABC发送到一个MessageQueue上,那么消费者消费就一定是按这个顺序消费的。

如何实现将这三条消息ABC发送到一个MessageQueue上呢?

4.1 顺序消息生产

public class Producer {

    public static void main(String[] args) {
        DefaultMQProducer producer = new DefaultMQProducer("fifo_test");
        producer.setNamesrvAddr("localhost:9876");
        try {
            producer.start();
            // 将100条消息按照除16余数的规则,分别发送到对应索引的MessageQueue中
            for (int i = 0; i < 100; i++) {
                Message message = new Message();
                message.setTopic("test_topic");
                message.setTags("tagA");
                message.setBody(new Integer(i).toString().getBytes(StandardCharsets.UTF_8));
                SendResult sendResult =producer.send(message, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        int id= (int) arg;
                        int index = id % mqs.size();
                        // 根据余数来判断消息投放的MessageQueue
                        return mqs.get(index);
                    }
                },i);
                System.out.printf("%s%n", sendResult);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

根据余数来判断消息投放的MessageQueue

4.2 顺序消息消费

public class Consumer {
    public static void main(String[] args) {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_test");
        consumer.setNamesrvAddr("localhost:9876");
        try {
            consumer.subscribe("test_topic","tagA");
            // 可以看到每个queue有唯一的consume线程来消费, 对每个queue(分区)有序
            consumer.registerMessageListener(new MessageListenerOrderly() {
                @Override
                public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                    ListIterator<MessageExt> iterator = msgs.listIterator();
                    while (iterator.hasNext()){
                        MessageExt next = iterator.next();
                        try {
                            System.out.println(Thread.currentThread().getName()+":"
                            +new String(next.getBody(),"utf-8")
                                    +"queueId=" + next.getQueueId() );
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                    return ConsumeOrderlyStatus.SUCCESS;
                }
            });
            consumer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
}

每一个队列都都有一个唯一的线程来进行消费,并且在每一个MessageQueue中,先发的消息先消费

5 延时消息

其实就是rocketmq收到消息后,不会立刻推送给消费者,而是要等待一段时间(由用户设置)再推送给消费者消费。

用法很简单,只需要在创建消息的时候指定消息的延时级别就行

public class Producer {

    public static void main(String[] args) {
        DefaultMQProducer producer = new DefaultMQProducer("producer_test");
        producer.setNamesrvAddr("localhost:9876");
        try {
            producer.start();
            Message message = new Message();
            message.setTopic("test_topic");
            message.setTags("tagA");
            message.setBody(("这是一条延时消息:"+ LocalDate.now().toString()).getBytes(StandardCharsets.UTF_8));
            // 设置延时等级3,这个消息将在10s之后发送消费者(现在只支持固定的几个时间,详看delayTimeLevel)
            message.setDelayTimeLevel(4);
            SendResult sendResult = producer.send(message);
            System.out.printf("%s%n", sendResult);
        } catch (MQClientException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (RemotingException e) {
            e.printStackTrace();
        } catch (MQBrokerException e) {
            e.printStackTrace();
        }
    }
}

消费者的代码和普通消费者一样,这里就不再贴出来了了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值