系列九(实战)、发送 & 接收顺序消息(Java操作RocketMQ)

一、发送 & 接收顺序消息

1.1、概述

        顺序消息是一种对消息发送和消费顺序有严格要求的消息。对于一个指定的Topic,消息严格按照先进先出(FIFO)的原则进行消息发布和消费,即先发布的消息先消费,后发布的消息后消费。在 Apache RocketMQ 中支持分区顺序消息,如下图所示。我们可以按照某一个标准对消息进行分区(比如图中的ShardingKey),同一个ShardingKey的消息会被分配到同一个队列中,并按照顺序被消费。需要注意的是 RocketMQ 消息的顺序性分为两部分,生产顺序性和消费顺序性。只有同时满足了生产顺序性和消费顺序性才能达到上述的FIFO效果。

1.2、生产顺序性

        RocketMQ 通过生产者和服务端的协议保障单个生产者串行地发送消息,并按序存储和持久化。如需保证消息生产的顺序性,则必须满足以下条件:

(一)单一生产者:

        消息生产的顺序性仅支持单一生产者,不同生产者分布在不同的系统,即使设置相同的分区键,不同生产者之间产生的消息也无法判定其先后顺序;

(二)串行发送:

        生产者客户端支持多线程安全访问,但如果生产者使用多线程并行发送,则不同线程间产生的消息将无法判定其先后顺序。

        满足以上条件的生产者,将顺序消息发送至服务端后,会保证设置了同一分区键的消息,按照发送顺序存储在同一队列中。服务端顺序存储逻辑如下:

1.3、应用场景

        顺序消息的应用场景,其实在现实生活中也是随处可见,例如:工厂里边的流水线、网上商城购物等,就拿网上商城购物来说,一般会经历如下几个环节:

下订单===>减库存===>增加积分,该顺序需要严格一致,否则交易系统就会紊乱。

1.4、Demo06MQTestApp

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/12/25 11:18
 * @Description: 发送 & 接收顺序消息
 */
@Slf4j
public class Demo06MQTestApp {


    /**
     * 发送顺序消息
     */
    @Test
    public void demo6Producer() throws Exception {
        // 1、创建一个生产者
        DefaultMQProducer producer = new DefaultMQProducer("orderly-producer-group");

        // 2、连接NameServer
        producer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);

        // 3、启动
        producer.start();

        for (Integer i = 1; i <= 4; i++) {
            for (int j = 1; j <= 3; j++) {
                String data = "";
                switch (j % 3) {
                    case 1:
                        data = "下订单,订单ID:" + i;
                        break;
                    case 2:
                        data = "减库存,订单ID:" + i;
                        break;
                    case 0:
                        data = "增加积分,订单ID:" + i;
                        break;
                }
                // 4、创建顺序消息
                Message message = new Message("orderly-topic","orderly-tag", i.toString(),data.getBytes(StandardCharsets.UTF_8));

                // 5、发送消息
                producer.send(message, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> queues, Message msg, Object o) {
                        int hashCode = message.getKeys().hashCode();
                        // 队列的数量
                        int size = queues.size();
                        // 队列的索引
                        int index = hashCode % size;
                        log.info("当前操作:{},hashCode:{},size:{},index:{},keys:{}",StrUtil.utf8Str(message.getBody()),hashCode,size,index,message.getKeys());
                        return queues.get(index);
                    }
                }, i);
            }
        }

        log.info("【demo6Producer】发送消息成功!");
        // 6、关闭生产者
        producer.shutdown();
    }

    /**
     * 接收顺序消息(Push方式)
     */
    @Test
    public void demo6PushConsumer1() throws Exception {
        // 1、创建一个消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly-consumer-group");
        // 2、连接NameServer
        consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
        // 3、订阅消息,*表示订阅该主题所有的消息
        consumer.subscribe("orderly-topic", "*");
        // 4、设置监听器(采用异步回调方式,一直监听)
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> messages, ConsumeOrderlyContext context) {
                for (MessageExt message : messages) {
                    log.info("我是消费者【demo6PushConsumer1】,我收到的消息是:{},队列ID:{}",StrUtil.utf8Str(message.getBody()),message.getQueueId());
                }
                /**
                 * 返回值:消费消息成功与否
                 *      SUCCESS:表明消费成功,消息会从MQ出队
                 */
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        // 5、启动
        consumer.start();
        log.info("【demo6PushConsumer1】启动成功,正在等待接收消息...");
        // 6、挂起当前JVM
        System.in.read();
    }

    @Test
    public void demo6PushConsumer2() throws Exception {
        // 1、创建一个消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly-consumer-group");
        // 2、连接NameServer
        consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
        // 3、订阅消息,*表示订阅该主题所有的消息
        consumer.subscribe("orderly-topic", "*");
        // 4、设置监听器(采用异步回调方式,一直监听)
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> messages, ConsumeOrderlyContext context) {
                for (MessageExt message : messages) {
                    log.info("我是消费者【demo6PushConsumer2】,我收到的消息是:{},队列ID:{}",StrUtil.utf8Str(message.getBody()),message.getQueueId());
                }
                /**
                 * 返回值:消费消息成功与否
                 *      SUCCESS:表明消费成功,消息会从MQ出队
                 */
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        // 5、启动
        consumer.start();
        log.info("【demo6PushConsumer2】启动成功,正在等待接收消息...");
        // 6、挂起当前JVM
        System.in.read();
    }

    @Test
    public void demo6PushConsumer3() throws Exception {
        // 1、创建一个消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly-consumer-group");
        // 2、连接NameServer
        consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
        // 3、订阅消息,*表示订阅该主题所有的消息
        consumer.subscribe("orderly-topic", "*");
        // 4、设置监听器(采用异步回调方式,一直监听)
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> messages, ConsumeOrderlyContext context) {
                for (MessageExt message : messages) {
                    log.info("我是消费者【demo6PushConsumer3】,我收到的消息是:{},队列ID:{}",StrUtil.utf8Str(message.getBody()),message.getQueueId());
                }
                /**
                 * 返回值:消费消息成功与否
                 *      SUCCESS:表明消费成功,消息会从MQ出队
                 */
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        // 5、启动
        consumer.start();
        log.info("【demo6PushConsumer3】启动成功,正在等待接收消息...");
        // 6、挂起当前JVM
        System.in.read();
    }

}

1.5、测试

        先后启动demo6PushConsumer1、demo6PushConsumer2、demo6PushConsumer3和demo6Producer,观察控制台日志输出信息。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值