RocketMQ入门(二)

消息发送样例

依赖:

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

消息发送者步骤分析

1.创建消息生产者producer,并制定生产者组名
2.指定Nameserver地址
3.启动producer
4.创建消息对象,指定主题Topic、Tag和消息体
5.发送消息
6.关闭生产者producer

消息消费者步骤分析

1.创建消费者Consumer,制定消费者组名
2.指定Nameserver地址
3.订阅主题Topic和Tag
4.设置回调函数,处理消息
5.启动消费者consumer

基本样例

消息发送

发送同步消息

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

/**
 * 发送同步消息
 */
public class SyncProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        for (int i = 0; i < 10; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message message = new Message("base", "Tag1", ("hello world" + i).getBytes());
            //5.发送消息
            SendResult send = producer.send(message);
            System.out.println(send.toString());
            //线程睡1秒
            TimeUnit.MILLISECONDS.sleep(1);
        }
        //6.关闭生产者producer
        producer.shutdown();
    }
}

在这里插入图片描述

发送异步消息

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

/**
 * 发送异步消息
 */
public class AsyncProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        //使用减法计数器防止主线程结束了,异步线程还没执行完
        final CountDownLatch countDownLatch = new CountDownLatch(10);
        
        for (int i = 0; i < 10; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message message = new Message("base", "Tag2", ("hello world" + i).getBytes());
            //5.发送异步消息
            producer.send(message, new SendCallback() {
                /**
                 * 发送成功回调函数
                 */
                public void onSuccess(SendResult sendResult) {
                    //减法计数器减一
                    countDownLatch.countDown();
                    System.out.println("发送结果:"+sendResult);
                }
                /**
                 * 发送失败回调函数
                 */
                public void onException(Throwable throwable) {
                    //减法计数器减一
                    countDownLatch.countDown();
                    System.out.println("发送异常:"+throwable);
                }
            });

        }
        //延迟主线程的执行时间,避免主线程结束了,异步线程还没执行完,出现No route info of this topic的异常
        //或者使用CountDownLatch解决
//        Thread.sleep(3000);
        //必要的任务执行完,等待计数器归零,才向下执行
        countDownLatch.await();
        //6.关闭生产者producer
        producer.shutdown();
    }
}

在这里插入图片描述

发送单向消息

这种方式主要用在不特别关心发送结果的场景,例如:日志发送。
没有返回值的,是不知道有没有发送成功,除非使用消费者去消费

/**
 * 单向发送消息
 */
public class OnewayProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        for (int i = 0; i < 10; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message message = new Message("base", "Tag3", ("hello world,单向消息" + i).getBytes());
            //5.发送单向消息,无返回值
            producer.sendOneway(message);
            //线程睡1秒
            TimeUnit.MILLISECONDS.sleep(1);
        }
        //6.关闭生产者producer
        producer.shutdown();
    }
}

消息消费

消费消息基本流程

因为之前测试同步,异步,单向消息发送时,已经往队列里添加了消息,现在测试消费者,直接去队列里消费即可,消费了消息,要发送者重新发送消息,才能继续消费。

/**
 * 消息的接受者
 * 1.创建消费者Consumer,制定消费者组名
 * 2.指定Nameserver地址
 * 3.订阅主题Topic和Tag
 * 4.设置回调函数,处理消息
 * 5.启动消费者consumer
 */
public class Consumer {
    private  static void  run() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名  推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("base","Tag1");
        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //接受消息内容
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(new String(messageExt.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
    }
    public static void main(String[] args) throws MQClientException {
        run();
    }
}

在这里插入图片描述

负载均衡模式

消费者采用负载均衡方式消费消息,多个消费者共同消费队列消息,每个消费者处理的消息不同。默认的消费模式。
在这里插入图片描述
启动上面的消费者三个,然后启动同步消息提供者。
提供者发送十条消息,看看十条消息是不是分散到三个消费者中消费。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
确实如此。

广播模式

消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的在这里插入图片描述

    /**
     * 广播模式
     */
    private  static void  run1() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名 推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("base","Tag1");

        //设置消费模式:广播模式
        consumer.setMessageModel(MessageModel.BROADCASTING);

        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //接受消息内容
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(new String(messageExt.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
    }
    public static void main(String[] args) throws MQClientException {
//        run();
        //广播模式
        run1();
    }

启动两个消费者,然后启动同步消息提供者。
提供者发送十条消息:
在这里插入图片描述
消费者1:
在这里插入图片描述
消费者2:
在这里插入图片描述
每个消费者消费的消息都是相同的!

顺序消息

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。
在这里插入图片描述
在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候以多线程的方式从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。
但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。
局部消息顺序:
在这里插入图片描述

一个topic在一个broker上创建队列数量defaultTopicQueueNums=8 默认为8

当发送和消费参与的queue只有一个,则是全局有序;
如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

一般来讲,我们只需要保证局部的顺序消息就可以了,说白了就是同一个业务相关的消息,发到同一个队列中就可以了,消费的时候,取队列的消息的时候,采用一个线程的方式去取同一个队列的消息。

下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。

顺序消息生产

构造消息集合:

/**
 * 订单的步骤
 */
public  class OrderStep {
   private long orderId;
   private String desc;
   public long getOrderId() {
       return orderId;
   }
   public void setOrderId(long orderId) {
       this.orderId = orderId;
   }
   public String getDesc() {
       return desc;
   }
   public void setDesc(String desc) {
       this.desc = desc;
   }
   @Override
   public String toString() {
       return "OrderStep{" +
           "orderId=" + orderId +
           ", desc='" + desc + '\'' +
           '}';
   }
   /**
    * 生成模拟订单数据 3个订单
    */
   public static List<OrderStep> buildOrders() {
       //1039  :创建 付款 推送 完成 取余3
       //1065  :创建 付款 完成   取余1
       //7235  :创建 付款 完成   取余3
       List<OrderStep> orderList = new ArrayList<OrderStep>();

       OrderStep orderDemo = new OrderStep();
       orderDemo.setOrderId(1039L);
       orderDemo.setDesc("创建");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(1065L);
       orderDemo.setDesc("创建");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(1039L);
       orderDemo.setDesc("付款");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(7235L);
       orderDemo.setDesc("创建");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(1065L);
       orderDemo.setDesc("付款");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(7235L);
       orderDemo.setDesc("付款");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(1065L);
       orderDemo.setDesc("完成");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(1039L);
       orderDemo.setDesc("推送");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(7235L);
       orderDemo.setDesc("完成");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(1039L);
       orderDemo.setDesc("完成");
       orderList.add(orderDemo);

       return orderList;
   }
}

生产者:

public class Producer {
    private static void run() throws MQClientException, InterruptedException, RemotingException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        //构建订单步骤集合
        List<OrderStep> orderSteps = OrderStep.buildOrders();

        for (int i = 0; i < orderSteps.size(); i++) {
            /**
             * 4.创建消息对象,指定主题Topic、Tag和消息体
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息key
             * 参数四:消息内容
             */
            Message message = new Message("OrderTopic", "order", "i"+i,orderSteps.get(i).toString().getBytes());
            /**
             * 5.发送消息
             * 参数一:消息对象
             * 参数二:消息队列选择器
             * 参数三:选择队列的业务标识(订单id)
             */
            SendResult sendResult = producer.send(message, new MessageQueueSelector() {
                /**
                 * 选择队列
                 * @param list 队列集合 默认四个队列,要修改的话broker配置文件defaultTopicQueueNums=x
                 * @param message 消息对象
                 * @param o 业务表示的参数
                 * @return
                 */
                @Override
                public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
                    Long orderId = (Long) o;
                    //取余,选取队列
                    long l = orderId % list.size();
                    return list.get((int) l);
                }
            }, orderSteps.get(i).getOrderId());

            System.out.println("发送结果:"+sendResult);
        }
        //6.关闭生产者producer
        producer.shutdown();
    }
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        run();
    }
}

顺序消费消息

public class Consumer {
    private  static void  run() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名 推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");

        //3.订阅主题Topic和Tag  *表示这个主题下的所有消息都消费
        consumer.subscribe("OrderTopic","*");
        //4.设置回调函数,处理消息
        //MessageListenerOrderly在进行消息消费的时候,对于一个队列的消息用一个线程处理
        consumer.registerMessageListener(new MessageListenerOrderly(){
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println("线程名:"+Thread.currentThread().getName()+"消费消息:"+new String(messageExt.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
        System.out.println("消费者启动---------------------------");
    }
    public static void main(String[] args) throws MQClientException {
        run();
    }
}

启动生产者
在这里插入图片描述
订单1065消息集合的消费顺序:创建->付款->完成 由线程2按顺序单独消费完
1039:创建->付款->推送->完成 由线程1按顺序单独消费完
7235:创建->付款->完成 由线程1按顺序单独消费完
在这里插入图片描述
提供者再次发送消息时,发现订单的消息消费顺序没变,但是由多个线程消费完
订单1065消息集合的消费顺序:创建->付款->完成 由线程4,7,9共同消费完
1039:创建->付款->推送->完成
7235:创建->付款->完成

开多个消费者来测试:也是能按顺序消费的,而且是负载均衡的模式去消费
在这里插入图片描述
在这里插入图片描述

延时消息

比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

生产者:其实就加一句代码message.setDelayTimeLevel(2);

/**
 * 发送同步消息
 */
public class SyncProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        for (int i = 0; i < 10; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message message = new Message("DelayTopic", "tag1", ("hello world" + i).getBytes());
            //设置延迟时间,延迟等级2
            message.setDelayTimeLevel(2);
            //5.发送消息
            SendResult send = producer.send(message);
            System.out.println(send.toString());
        }
        //6.关闭生产者producer
        producer.shutdown();
    }
}

消费者:

public class DelayConsumer {
    private  static void  run() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名 推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("DelayTopic","tag1");
        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //接受消息内容
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println("消息ID:"+messageExt.getMsgId()+" 延迟时间:"+(System.currentTimeMillis()-messageExt.getStoreTimestamp()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
        System.out.println("消费者启动");
    }
    public static void main(String[] args) throws MQClientException {
        run();
    }
}

在这里插入图片描述
使用限制:
现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18

// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

为什么开源的RocketMQ没有支持任意精度的延时消息?

https://zhuanlan.zhihu.com/p/99373081

批量消息

批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB

生产者:把消息放到集合里,直接send,不用for循环一个一个send

/**
 * 发送同步消息
 */
public class BatchProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        
        //4.批量发送消息
        List<Message> messageList=new ArrayList<Message>();
        Message message1=new Message("base","tag1",("hello"+1).getBytes());
        Message message2=new Message("base","tag1",("hello"+2).getBytes());
        Message message3=new Message("base","tag1",("hello"+3).getBytes());
        messageList.add(message1);
        messageList.add(message2);
        messageList.add(message3);
        SendResult sendResult = producer.send(messageList);
        System.out.println("发送结果:"+sendResult);
        //6.关闭生产者producer
        producer.shutdown();
    }
}

消费者:

public class DatchConsumer {
    private  static void  run() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名 推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("base","tag1");
        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //接受消息内容
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(messageExt);
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
        System.out.println("消费者启动");
    }
    public static void main(String[] args) throws MQClientException {
        run();
    }
}

如果消息的总长度可能大于4MB时,这时候最好把消息进行分割:

public class ListSplitter implements Iterator<List<Message>> {
   private final int SIZE_LIMIT = 1024 * 1024 * 4;
   private final List<Message> messages;
   private int currIndex;
   public ListSplitter(List<Message> messages) {
           this.messages = messages;
   }
    @Override 
    public boolean hasNext() {
       return currIndex < messages.size();
   }
   	@Override 
    public List<Message> next() {
       int nextIndex = currIndex;
       int totalSize = 0;
       for (; nextIndex < messages.size(); nextIndex++) {
           Message message = messages.get(nextIndex);
           int tmpSize = message.getTopic().length() + message.getBody().length;
           Map<String, String> properties = message.getProperties();
           for (Map.Entry<String, String> entry : properties.entrySet()) {
               tmpSize += entry.getKey().length() + entry.getValue().length();
           }
           tmpSize = tmpSize + 20; // 增加日志的开销20字节
           if (tmpSize > SIZE_LIMIT) {
               //单个消息超过了最大的限制
               //忽略,否则会阻塞分裂的进程
               if (nextIndex - currIndex == 0) {
                  //假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环
                  nextIndex++;
               }
               break;
           }
           if (tmpSize + totalSize > SIZE_LIMIT) {
               break;
           } else {
               totalSize += tmpSize;
           }

       }
       List<Message> subList = messages.subList(currIndex, nextIndex);
       currIndex = nextIndex;
       return subList;
   }
}
//把大的消息分裂成若干个小的消息
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
  try {
      List<Message>  listItem = splitter.next();
      producer.send(listItem);
  } catch (Exception e) {
      e.printStackTrace();
      //处理error
  }
}

过滤消息

在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如:

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
//*表示这个主题下的所有消息都消费
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");

消费者将接收包含TAGATAGBTAGC的消息。
还可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:

------------
| message  |
|----------|  a > 5 AND b = 'abc'
| a = 10   |  --------------------> Gotten
| b = 'abc'|
| c = true |
------------
------------
| message  |
|----------|   a > 5 AND b = 'abc'
| a = 1    |  --------------------> Missed
| b = 'abc'|
| c = true |
------------

SQL基本语法

RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。

  • 数值比较,比如:>,>=,<,<=,BETWEEN,=;
  • 字符比较,比如:=,<>,IN;
  • IS NULL 或者 IS NOT NULL;
  • 逻辑符号 AND,OR,NOT;

常量支持类型为:

  • 数值,比如:123,3.1415;
  • 字符,比如:‘abc’,必须用单引号包裹起来;
  • NULL,特殊的常量
  • 布尔值,TRUEFALSE

只有使用push模式的消费者才能用使用SQL92标准的sql语句,接口如下:

public void subscribe(finalString topic, final MessageSelector messageSelector)

消息生产者:

public class SqlProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");
        //3.启动producer
        producer.start();
        for (int i = 0; i < 10; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message message = new Message("FilterTagTopic", "Tag1", ("hello world" + i).getBytes());
            //给消息设置属性
            message.putUserProperty("i",String.valueOf(i));

            //5.发送消息
            SendResult send = producer.send(message);
            System.out.println(send.toString());
        }
        //6.关闭生产者producer
        producer.shutdown();
    }
}

消息消费者:

public class SqlConsumer {
    private  static void  run() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名 推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("FilterTagTopic", MessageSelector.bySql("i > 3"));
        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //接受消息内容
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(messageExt);
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
        System.out.println("消费者启动");
    }
    public static void main(String[] args) throws MQClientException {
        run();
    }
}

事务消息

在这里插入图片描述RocketMQ消息的发送分成2个阶段:Prepare阶段和确认阶段

prepare阶段

  • 生产者发送一个不完整的事务消息——HalfMsg到消息中间件,消息中间件会为这个HalfMsg生成一个全局唯一标识,生产者可以持有标识,以便下一阶段找到这个HalfMsg
  • 生产者执行本地事务。

注意:消费者无法立刻消费HalfMsg,生产者可以对HalfMsg进行Commit或者Rollback来终结事务。只有当CommitHalfMsg后,消费者才能消费到这条消息。

确认阶段

  • 如果生产者执行本地事务成功,就向消息中间件发送一个Commit消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已提交】,然后通知消费者执行事务;
  • 如果生产者执行本地事务失败,就向消息中间件发送一个Rollback消息(包含之前HalfMsg的唯一标识),中间件修改HalfMsg的状态为【已取消】。

消息中间件会定期去向生产者询问,是否可以Commit或者Rollback那些由于错误没有被终结的HalfMsg,以此来结束它们的生命周期,以达成事务最终的一致。之所以需要这个询问机制,是因为生产者可能提交完本地事务,还没来得及对HalfMsg进行Commit或者Rollback,就挂掉了,这样就会处于一种不一致状态。

ACK机制
消费者消费完消息后,可能因为自身异常,导致业务执行失败,此时就必须要能够重复消费消息。RocketMQ提供了ACK机制,即RocketMQ只有收到服务消费者的ack message后才认为消费成功。

所以,服务消费者可以在自身业务员逻辑执行成功后,向RocketMQ发送ack message,保证消费逻辑执行成功。

生产者:
与之前不同的地方
得用事务生产者TransactionMQProducer
还得设置事务监听器setTransactionListener
发送事务消息;

public class TranProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        TransactionMQProducer producer = new TransactionMQProducer("group2");
        //2.指定Nameserver地址,集群地址用逗号隔开
        producer.setNamesrvAddr("192.168.59.131:9876");

        //设置事务监听器
        producer.setTransactionListener(new TransactionListener() {
            /**
             * 生产者在该方法中执行本地的事务
             * @param message
             * @param o
             * @return
             */
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                if (StringUtils.equals("tag1",message.getTags())){
                    return LocalTransactionState.COMMIT_MESSAGE;
                }else if (StringUtils.equals("tag2",message.getTags())){
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                }else if (StringUtils.equals("tag3",message.getTags())){

                    //即不提交事务也不回滚事务,因为询问机制,mq会回查是否可以Commit或者Rollback那些由于错误没有被终结的HalfMsg
                    return LocalTransactionState.UNKNOW;
                }
                return LocalTransactionState.UNKNOW;
            }
            /**
             * 该方法是mq进行消息事务状态回查
             * @param messageExt
             * @return
             */
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                System.out.println("消息的tag:"+messageExt.getTags());
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        //3.启动producer
        producer.start();
        System.out.println("生产者启动");

        String[] tag={"tag1","tag2","tag3"};
        for (int i = 0; i < 3; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message message = new Message("TransactionTopic", tag[i], ("hello world" + i).getBytes());
            //5.发送事务消息
            //第二个参数,在进行事务控制的时候,可以将事务应用到某一个消息上,
            //也可以应用到producer上,该producer发送的消息都会进行事务控制
            //null表示应用到producer
            SendResult send = producer.sendMessageInTransaction(message,null);
            System.out.println(send.toString());
            //线程睡1秒
            TimeUnit.MILLISECONDS.sleep(1);
        }
        //6.关闭生产者producer
        //由于mq要回查生产者,所以不要停了生产者
//        producer.shutdown();
    }
}

消费者:与之前没啥不同

public class TranConsumer {
    private  static void  run() throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名 推模式Broker将消息推给消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("192.168.59.131:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("TransactionTopic","*");
        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            //接受消息内容
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(messageExt.getTags());
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();
        System.out.println("消费者启动");
    }
    public static void main(String[] args) throws MQClientException {
        run();
    }
}

测试:
生产者成功发送三条消息到mq,但是tag2的消息被回滚了,tag3的消息先是即不回滚也不提交,然后被mq进行消息事务状态回查时,给提交了。所以猜测消费者能消费tag1tag3的消息。
在这里插入图片描述
消费者:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42412601/article/details/107379052