RocketMQ—RocketMQ发送同步、异步、单向、延迟、批量、顺序、批量消息、带标签消息

RocketMQ---RocketMQ发送同步、异步、单向、延迟、批量、顺序、批量消息、带标签消息

发送同步消息

生产者发送消息,mq进行确认,然后返回给生产者状态。这就是同步消息。

前文demo程序就是发送的同步消息。

发送异步消息

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

代码如下:

java 复制代码
/**
     * 异步消息测试
     */
@Test
public void simpleAsyncProducer() throws Exception {
    //创建一个生产者,并指定一个组名
    DefaultMQProducer producer = new DefaultMQProducer("async-producer-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    //启动
    producer.start();


    //指定topic,创建一个消息
    Message message = new Message("asyncTopic1", "这是一条异步消息".getBytes());

    //发送异步消息,并设置回调内容
    producer.send(message, new SendCallback() {
        @Override
        public void onSuccess(SendResult sendResult) {
            log.info("回调内容,发送成功");
        }

        @Override
        public void onException(Throwable throwable) {
            log.info("回调内容,发送失败");
        }
    });


    log.info("主线程执行中=========");

    System.in.read();
}

从运行结果可以看到是不同的线程输出的内容。

发送单向消息

这种方式主要用在不关心发送结果的场景,这种方式吞吐量很大,但是存在消息丢失的风险,例如日志信息的发送。

代码如下:

java 复制代码
@Test
public void oneWayMessageTest() throws Exception {
    //创建一个生产者,并指定一个组名
    DefaultMQProducer producer = new DefaultMQProducer("oneway-producer-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    //启动
    producer.start();


    //指定topic,创建一个消息
    Message message = new Message("onewayTopic1", "这是一条单向消息".getBytes());

    //发送单向消息
    producer.sendOneway(message);

    producer.shutdown();
}

发送延迟消息

消息放入mq后,过一段时间,才会被监听到,然后消费.

比如下订单业务,提交了一个订单就可以发送一个延时消息,30min后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

代码如下

java 复制代码
@Test
public void msMessageTest() throws Exception{
    //创建一个生产者,并指定一个组名
    DefaultMQProducer producer = new DefaultMQProducer("ms-producer-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    //启动
    producer.start();

    //指定topic,创建一个消息
    Message message = new Message("msTopic1", "这是一条单向消息".getBytes());
    //给消息设置一个延迟时间
    message.setDelayTimeLevel(3);

    //发送延时消息
    producer.sendOneway(message);

    producer.shutdown();
}

延时等级如下:

发送批量消息

代码如下:

java 复制代码
@Test
public void testBatchProducer() throws Exception {
    // 创建默认的生产者
    DefaultMQProducer producer = new DefaultMQProducer("test-batch-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    // 启动实例
    producer.start();
    List<Message> msgs = Arrays.asList(
        new Message("batchTopicTest", "我是一组消息的A消息".getBytes()),
        new Message("batchTopicTest", "我是一组消息的B消息".getBytes()),
        new Message("batchTopicTest", "我是一组消息的C消息".getBytes())

    );
    SendResult send = producer.send(msgs);
    System.out.println(send);
    // 关闭实例
    producer.shutdown();
}

这些消息会被放到同一个队列中。

发送顺序消息

可以想象一个场景,我们在网上购物时,需要先完成下订单操作,然后再去发短信,再进行发货,需要保证顺序的。

前文我们讲的都是并发消息,这种消息并不能完成上述的场景逻辑。比如一个topic里有10个消息,分别在4个队列中;

  • 如果消费者,同时有20个线程在消费,可能A线程拿到消息1了,B线程拿到消息2了,但是B线程可能完成的比A线程早,这就没办法上述场景的顺序了。
  • 如果消费者只有一个线程,轮询消费四个队列中的消息时,也不能保证是网购场景中的顺序的。

这就要引出顺序消息:把消费者变为单线程,把下订单消息、发短信消息、发货消息放到同一个队列就可以了。

代码

消息封装成实体类如下:

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MessageModel {
    //订单id
    private String orderId;
    //用户id
    private String userId;
    //消息描述
    private String description;
}

发送顺序消息的生产者代码如下:

java 复制代码
/**
     * 顺序消息
     */

@Test
public void testOrderlyProducer() throws Exception {

    List<MessageModel> messageModelList = Arrays.asList(
        //用户1的订单
        new MessageModel("order-111","user-1","下单"),
        new MessageModel("order-111","user-1","发短信"),
        new MessageModel("order-111","user-1","发货"),

        //用户2的订单
        new MessageModel("order-222","user-2","下单"),
        new MessageModel("order-222","user-2","发短信"),
        new MessageModel("order-222","user-2","发货")
    );

    // 创建默认的生产者
    DefaultMQProducer producer = new DefaultMQProducer("test-orderly-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    // 启动实例
    producer.start();

    //发送顺序消息时 发送时相同用户的消息要保证有序,并且发到同一个队列里
    messageModelList.forEach(
        messageModel->{
            Message message = new Message("orderlyTopic", messageModel.toString().getBytes());
            try {
                //发送消息,相同订单号去相同队列
                producer.send(message, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message message, Object arg) {
                        //producer.send(message,selector,arg),第三个参数订单号会传给selector要实现的方法的arg
                        //在这里选择队列
                        int hashCode = Math.abs(arg.toString().hashCode());
                        int index = hashCode % mqs.size();
                        return mqs.get(index);
                    }
                }, messageModel.getOrderId());
            } catch (Exception e) {
                log.error("有错误发生",e);
            }
        }
    );
    // 关闭实例
    producer.shutdown();
    log.info("发送完成");
}

消费顺序消息的消费者代码如下:

java 复制代码
//消费者
@Test
public void orderlyConsumer() throws Exception {
    //创建一个消费者,并指定一个组名
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-orderly-consumer-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    consumer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    //订阅一个主题 *号表示订阅这个主题中所有的消息
    consumer.subscribe("orderlyTopic","*");
    //设置一个监听器(一直监听,异步回调方式)
    consumer.registerMessageListener(new MessageListenerOrderly() {
        @Override
        public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
            log.info("线程id"+Thread.currentThread().getId());
            log.info("消息内容:"+new String(msgs.get(0).getBody()));
            return ConsumeOrderlyStatus.SUCCESS;
        }
    });

    //启动消费者
    consumer.start();

    //挂起当前jvm,防止主线程结束,让监听器一直监听
    System.in.read();

}

运行结果如下:

可以看到同一个订单是顺序消费的。

其他问题

如果我们的消息消费失败了怎么办?

如果是并发模式,消费失败会进行重试,重试16次后还会没消费成功,会被放到死信队列里。

如果是顺序模式,如果重试失败,会无限重试,是int的最大值。

发送带标签的消息,消息过滤

如果我们有衣服订单的消息、手机订单的消息,如果我们只使用topic进行区分,就要使用两个topic;但是它们都是订单,所以在同一个topic中会好一些,Rocketmq就提供了消息过滤功能,通过tag或者key进行区分。

生产者代码如下:

java 复制代码
@Test
public void testTagProducer() throws Exception {
    // 创建默认的生产者
    DefaultMQProducer producer = new DefaultMQProducer("test-tag-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    // 启动实例
    producer.start();
    Message messageTopic1 = new Message("tagTopic", "tag1", "这是topic1的消息".getBytes());
    Message messageTopic2 = new Message("tagTopic", "tag2", "这是topic2的消息".getBytes());
    producer.send(messageTopic1);
    producer.send(messageTopic2);
    // 关闭实例
    producer.shutdown();
}

消费tag1的消费者

java 复制代码
//消费tag1的消费者
@Test
public void tagConsumer1() throws Exception {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group-a");
    consumer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    consumer.subscribe("tagTopic", "tag1");
    consumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            System.out.println("我是tag1的消费者,我正在消费消息" + new String(msgs.get(0).getBody()));
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });
    consumer.start();
    System.in.read();
}

消费tag1和tag2的消费者

java 复制代码
//消费tag1和tag2的消费者
@Test
public void tagConsumer2() throws Exception {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group-a");
    consumer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    consumer.subscribe("tagTopic", "tag1 || tag2");
    consumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            System.out.println("我是tag1和tag2的消费者,我正在消费消息" + new String(msgs.get(0).getBody()));
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });
    consumer.start();
    System.in.read();
}

带key的消息

消息都会有自己的MessageId的,如下图:

那我们能否指定id呢?

在发送消息时可以指定key:

java 复制代码
@Test
public void testKeyProducer() throws Exception {
    // 创建默认的生产者
    DefaultMQProducer producer = new DefaultMQProducer("test-key-group");
    //连接namesrv,参数是namesrv的ip地址:端口号
    producer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);

    String key = UUID.randomUUID().toString();

    // 启动实例
    producer.start();
    Message messageTopic1 = new Message("keyTopic", "tag1",key, "这是topic1的消息".getBytes());
    producer.send(messageTopic1);
    // 关闭实例
    producer.shutdown();
}

消费者获取key:

java 复制代码
@Test
public void testKeyConsumer() throws Exception {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("key-consumer-group-a");
    consumer.setNamesrvAddr(MqConstant.NAME_SERVER_ADDR);
    consumer.subscribe("keyTopic","*");
    consumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            System.out.println("我们设置的key:" + msgs.get(0).getKeys());
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });
    consumer.start();
    System.in.read();
}

输出如下:

相关推荐
不想睡觉的橘子君1 天前
【MQ】RabbitMQ、RocketMQ、kafka特性对比
kafka·rabbitmq·rocketmq
厌世小晨宇yu.2 天前
RocketMQ学习笔记
笔记·学习·rocketmq
Java 第一深情3 天前
RabbitMQ最全教程-Part1(基础使用)
分布式·消息队列·rabbitmq
洛卡卡了3 天前
如何选择最适合的消息队列?详解 Kafka、RocketMQ、RabbitMQ 的使用场景
kafka·rabbitmq·rocketmq
菜鸟起航ing4 天前
Spring Cloud Alibaba
spring cloud·java-ee·rocketmq
乄bluefox4 天前
学习RocketMQ(记录了个人艰难学习RocketMQ的笔记)
java·spring boot·中间件·rocketmq
ktkiko114 天前
Java中消息队列——ActiveMQ、RabbitMQ、RocketMQ、Kafka
java·消息队列
虽千万人 吾往矣7 天前
golang rocketmq开发
开发语言·golang·rocketmq
HippoSystem7 天前
[RocketMQ 5.3.1] Win11 + Docker Desktop 本地部署全流程 + 踩坑记录
rocketmq
确实可以10 天前
7.使用Redis进行秒杀优化
redis·消息队列·go