部分内容出处   https://www.jianshu.com/p/453c6e7ff81c

rocketmq内部有4个默认的队里,在发送消息时,同一组的消息需要按照顺序,发送到相应的mq中,同一组消息按照顺序进行消费,不同组的消息可以并行的进行消费。

下面看一下producer的代码:

  1. package com.alibaba.rocketmq.example.message.order;
  2.  
  3. import com.alibaba.rocketmq.client.exception.MQBrokerException;
  4. import com.alibaba.rocketmq.client.exception.MQClientException;
  5. import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
  6. import com.alibaba.rocketmq.client.producer.MessageQueueSelector;
  7. import com.alibaba.rocketmq.client.producer.SendResult;
  8. import com.alibaba.rocketmq.common.message.Message;
  9. import com.alibaba.rocketmq.common.message.MessageQueue;
  10. import com.alibaba.rocketmq.remoting.exception.RemotingException;
  11.  
  12. import java.text.SimpleDateFormat;
  13. import java.util.Date;
  14. import java.util.List;
  15.  
  16. /**
  17. * @author : Jixiaohu
  18. * @Date : 2018-04-19.
  19. * @Time : 9:20.
  20. * @Description :
  21. */
  22. public class Producer {
  23. public static void main(String[] args) throws MQClientException, InterruptedException, MQBrokerException {
  24. String groupName = "order_producer";
  25. DefaultMQProducer producer = new DefaultMQProducer(groupName);
  26. producer.setNamesrvAddr("192.168.1.114:9876;192.168.2.2:9876");
  27. producer.start();
  28.  
  29. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  30. String dateStr = sdf.format(new Date());
  31. try {
  32. for (int i = 1; i <= 3; i++) {
  33. String body = dateStr + "Hello RoctetMq : " + i;
  34. Message msg = new Message("Topic1", "Tag1", "Key" + i,
  35. body.getBytes());
  36. SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
  37. @Override
  38. public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
  39. Integer id = (Integer) o;
  40. return list.get(id);
  41. }
  42. }, 0); //0是队列的下标
  43. System.out.println(sendResult);
  44. }
  45. for (int i = 1; i <= 3; i++) {
  46. String body = dateStr + "Hello RoctetMq : " + i;
  47. Message msg = new Message("Topic1", "Tag1", "Key" + i,
  48. body.getBytes());
  49. SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
  50. @Override
  51. public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
  52. Integer id = (Integer) o;
  53. return list.get(id);
  54. }
  55. }, 1); //1是队列的下标
  56. System.out.println(sendResult);
  57. }
  58. for (int i = 1; i <= 3; i++) {
  59. String body = dateStr + "Hello RoctetMq : " + i;
  60. Message msg = new Message("Topic1", "Tag1", "Key" + i,
  61. body.getBytes());
  62. SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
  63. @Override
  64. public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
  65. Integer id = (Integer) o;
  66. return list.get(id);
  67. }
  68. }, 2); //2是队列的下标
  69. System.out.println(sendResult);
  70. }
  71. for (int i = 1; i <= 3; i++) {
  72. String body = dateStr + "Hello RoctetMq : " + i;
  73. Message msg = new Message("Topic1", "Tag1", "Key" + i,
  74. body.getBytes());
  75. SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
  76. @Override
  77. public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
  78. Integer id = (Integer) o;
  79. return list.get(id);
  80. }
  81. }, 3); //3是队列的下标
  82. System.out.println(sendResult);
  83. }
  84. for (int i = 1; i <= 3; i++) {
  85. String body = dateStr + "Hello RoctetMq : " + i;
  86. Message msg = new Message("Topic1", "Tag1", "Key" + i,
  87. body.getBytes());
  88. SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
  89. @Override
  90. public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
  91. Integer id = (Integer) o;
  92. return list.get(id);
  93. }
  94. }, 4); //4是队列的下标
  95. System.out.println(sendResult);
  96. }
  97. for (int i = 1; i <= 3; i++) {
  98. String body = dateStr + "Hello RoctetMq : " + i;
  99. Message msg = new Message("Topic1", "Tag1", "Key" + i,
  100. body.getBytes());
  101. SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
  102. @Override
  103. public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
  104. Integer id = (Integer) o;
  105. return list.get(id);
  106. }
  107. }, 5); //5是队列的下标
  108. System.out.println(sendResult);
  109. }
  110. } catch (RemotingException e) {
  111. e.printStackTrace();
  112. Thread.sleep(1000);
  113. }
  114. producer.shutdown();
  115. }
  116. }

这边发送多组消息,每组消息的顺序分别为1,2,3,

下面查看consumer1,和consumer2,因为要顺序消费,需要注意的是,这两个消费者的监听器是MessageListenerOrderly,两个的代码一样,我这边就只展示一个consumer的代码

  1. package com.alibaba.rocketmq.example.message.order;
  2.  
  3. import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
  4. import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
  5. import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
  6. import com.alibaba.rocketmq.client.consumer.listener.MessageListenerOrderly;
  7. import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
  8. import com.alibaba.rocketmq.common.message.MessageExt;
  9.  
  10. import java.util.List;
  11. import java.util.Random;
  12. import java.util.concurrent.TimeUnit;
  13.  
  14. /**
  15. * @author : Jixiaohu
  16. * @Date : 2018-04-23.
  17. * @Time : 9:35.
  18. * @Description : 顺序消息消费
  19. */
  20. public class Consumer1 {
  21.  
  22. public Consumer1() throws Exception {
  23. String groupName = "order_producer";
  24. DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
  25. consumer.setNamesrvAddr("192.168.1.114:9876;192.168.2.2:9876");
  26. /**
  27. * 设置Consumer第一次启动是从队列头开始消费还是队列尾开始消费
  28. * 非第一次启动,那么按照上次消费的位置继续消费
  29. */
  30. consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
  31. //订阅的主题,以及过滤的标签内容
  32. consumer.subscribe("Topic1", "*");
  33. //注册监听
  34. consumer.registerMessageListener(new Listener());
  35. consumer.start();
  36. System.out.println("Consumer1 Started.");
  37. }
  38.  
  39. class Listener implements MessageListenerOrderly {
  40.  
  41. private Random random = new Random();
  42.  
  43. @Override
  44. public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext context) {
  45. // 设置自动提交
  46. context.setAutoCommit(true);
  47. for (MessageExt msg : list) {
  48. System.out.println(msg + ",context" + new String(msg.getBody()));
  49. }
  50. try {
  51. TimeUnit.SECONDS.sleep(random.nextInt(1));
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55.  
  56. return ConsumeOrderlyStatus.SUCCESS;
  57. }
  58. }
  59.  
  60. public static void main(String[] args) throws Exception {
  61. new Consumer1();
  62. }
  63.  
  64. }

还是按照先启动consumer的顺序,在启动producer的顺序。

这边看一下控制台的信息

总共6组消息,broker-a上接收到4组消息,broker-b上接收到2组消息,同一组的3条消息会发送到同一个broker的同一个队列中,这样才能保证顺序消费,

下面看一下consumer1和consumer2的打印结果

由于顺序消费只能单线程,一个线程只能去一个队列获取数据。

可以看到,这边的queueid都是3个 3个打印,不会出现交替,下面看一下一组消息的消费顺序,

可以看到,消息是按照发送的顺序,进行消费,consumer2的打印结果也是类似的,不过consumer2消费了6条消息。

这样就实现了rocket的顺序消费,虽然实现了顺序消费,由于网络通信,会存在着重复数据的问题,

重复数据的问题,rocket不提供解决方案,让业务方自行解决,主要有两个方法:

  1. 消费端处理消息的业务逻辑保持幂等性
  2. 保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现

第1条很好理解,只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。第2条原理就是利用一张日志表来记录已经处理成功的消息的ID,如果新到的消息ID已经在日志表中,那么就不再处理这条消息。

第1条解决方案,很明显应该在消费端实现,不属于消息系统要实现的功能。第2条可以消息系统实现,也可以业务端实现。正常情况下出现重复消息的概率其实很小,如果由消息系统来实现的话,肯定会对消息系统的吞吐量和高可用有影响,所以最好还是由业务端自己处理消息重复的问题,这也是RocketMQ不解决消息重复的问题的原因。

RocketMQ不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重。

下面把consumer修改成多线程的模式,再次查看一下运行的结果:

  1. package com.alibaba.rocketmq.example.message.thread;
  2.  
  3. import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
  4. import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
  5. import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
  6. import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
  7. import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
  8. import com.alibaba.rocketmq.common.message.MessageExt;
  9.  
  10. import java.util.List;
  11. import java.util.Random;
  12. import java.util.concurrent.TimeUnit;
  13.  
  14. /**
  15. * @author : Jixiaohu
  16. * @Date : 2018-04-23.
  17. * @Time : 9:35.
  18. * @Description : 顺序消息消费
  19. */
  20. public class Consumer {
  21.  
  22. public Consumer() throws Exception {
  23. String groupName = "order_producer";
  24. DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
  25. consumer.setNamesrvAddr("192.168.1.114:9876;192.168.2.2:9876");
  26. /**
  27. * 设置Consumer第一次启动是从队列头开始消费还是队列尾开始消费
  28. * 非第一次启动,那么按照上次消费的位置继续消费
  29. */
  30. consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
  31.  
  32. //最小线程数
  33. consumer.setConsumeThreadMin(10);
  34.  
  35. //最大线程数
  36. consumer.setConsumeThreadMax(20);
  37.  
  38. //订阅的主题,以及过滤的标签内容
  39. consumer.subscribe("Topic1", "*");
  40. //注册监听
  41. consumer.registerMessageListener(new Listener());
  42. consumer.start();
  43. System.out.println("Consumer Started.");
  44. }
  45.  
  46. class Listener implements MessageListenerConcurrently {
  47.  
  48. private Random random = new Random();
  49.  
  50. @Override
  51. public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) {
  52.  
  53. for (MessageExt msg : list) {
  54. System.out.println(msg + ",context" + new String(msg.getBody()));
  55. }
  56. try {
  57. TimeUnit.SECONDS.sleep(random.nextInt(1));
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61.  
  62. return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
  63. }
  64. }
  65.  
  66. public static void main(String[] args) throws Exception {
  67. new Consumer();
  68. }
  69.  
  70. }

同样先启动consumer,在启动producer,查看一下打印结果:

从打印结果,可以看出consumer消费并不能保证严格的顺序,多线程和顺序,只能保证其中的一个。producer能保证消息发送的顺序,不能保证消息消费的顺序,要保证消息消费的顺序,consumer端必须实现 MessageListenerOrderly 接口。

RocketMq顺序消费的更多相关文章

  1. RocketMQ(7)---RocketMQ顺序消费

    RocketMQ顺序消费 如果要保证顺序消费,那么他的核心点就是:生产者有序存储.消费者有序消费. 一.概念 1.什么是无序消息 无序消息 无序消息也指普通的消息,Producer 只管发送消息,Co ...

  2. 一次 RocketMQ 顺序消费延迟的问题定位

    一次 RocketMQ 顺序消费延迟的问题定位 问题背景与现象 昨晚收到了应用报警,发现线上某个业务消费消息延迟了 54s 多(从消息发送到MQ 到被消费的间隔): 2021-06-30T23:12: ...

  3. RocketMQ 顺序消费只消费一次 坑

    rocketMq实现顺序消费的原理 produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一 ...

  4. 51.RocketMQ 顺序消费

    大部分的员工早上的心情可能不会很好,因为这时想到还有很多事情要做,压力会大点,一般到下午4点左右,状态会是一天中最好的,因为这时大部分的工作做得差不多了,又快要下班了,当然也不是绝对.要注意记录各下属 ...

  5. RocketMQ专题2:三种常用生产消费方式(顺序、广播、定时)以及顺序消费源码探究

    顺序.广播.定时任务 前插 ​ 在进行常用的三种消息类型例子展示的时候,我们先来说一说RocketMQ的几个重要概念: PullConsumer与PushConsumer:主要区别在于Pull与Pus ...

  6. RocketMQ事务消费和顺序消费详解

    一.RocketMq有3中消息类型 1.普通消费 2. 顺序消费 3.事务消费 顺序消费场景 在网购的时候,我们需要下单,那么下单需要假如有三个顺序,第一.创建订单 ,第二:订单付款,第三:订单完成. ...

  7. 【转】RocketMQ事务消费和顺序消费详解

    RocketMQ事务消费和顺序消费详解 转载说明:该文章纯转载,若有侵权或给原作者造成不便望告知,仅供学习参考. 一.RocketMq有3中消息类型 1.普通消费 2. 顺序消费 3.事务消费 顺序消 ...

  8. 分布式消息队列RocketMQ&Kafka -- 消息的“顺序消费”

    在说到消息中间件的时候,我们通常都会谈到一个特性:消息的顺序消费问题.这个问题看起来很简单:Producer发送消息1, 2, 3... Consumer按1, 2, 3...顺序消费. 但实际情况却 ...

  9. RocketMQ的顺序消费和事务消费

    一.三种消费 :1.普通消费 2. 顺序消费 3.事务消费 1.1  顺序消费:在网购的时候,我们需要下单,那么下单需要假如有三个顺序,第一.创建订单 ,第二:订单付款,第三:订单完成.也就是这个三个 ...

随机推荐

  1. CSS属性组-动画、转换、渐变

    一.动画 animation动画属性是一个简写属性,用于设置六个动画属性 aninmation-name动画名称,被调用 animation-duration完成动画需要的持续时间 animation ...

  2. Oracle数据文件迁移到裸设备

    本文主要描述如何将Oracle表空间的文件系统形式的数据文件迁移到LV裸设备上. 前提条件 1.oracle运行正常. 2.已使用LVM命令规划好LV文件.如/dev/vgoracle/lvdatat ...

  3. LeetCode OJ 24. Swap Nodes in Pairs

    Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2-&g ...

  4. 软件工程github使用小结

    1.在 https://github.com/join 这个网址处申请注册一个Github账号,申请成功后可在https://github.com/login 处利用刚刚注册的账号进行登录,才能开始在 ...

  5. 24.类的加载机制和反射.md

    目录 1类的加载连接和初始化 1.1类的加载过程 1.2类的加载器 1.2.1类的加载机制 1类的加载连接和初始化 1.1类的加载过程 类的加载过程简单为分为三步:加载->连接->初始化 ...

  6. 吴裕雄 29-MySQL 处理重复数据

    MySQL 处理重复数据有些 MySQL 数据表中可能存在重复的记录,有些情况我们允许重复数据的存在,但有时候我们也需要删除这些重复的数据.本章节我们将为大家介绍如何防止数据表出现重复数据及如何删除数 ...

  7. linux下卸载自带的JDK和安装想要的JDK

    卸载 1.卸载用 bin文件安装的JDK方法:      删除/usr/java目录下的所有东西 2.卸载系统自带的jdk版本方法: 查看自带的jdk: #rpm -qa | grep gcj 看到如 ...

  8. linux服务器中Jenkins集成git、Gradle持续构建Springboot项目

    Jenkins是用java编写的开源持续集成工具,目前被国内外各公司广泛使用.本章教大家如何在linux服务器中使用Jenkins自动发布一个可作为linux服务发布的Springboot项目. 自动 ...

  9. hive 安装centos7

    wget mirror.bit.edu.cn/apache/hive/hive-2.3.4/apache-hive-2.3.4-bin.tar.gz 解压到/usr/local/apache-hive ...

  10. 2-glance 部署

    1. mysql 创建数据库和用户 create database glance; grant all privileges on glance.* to 'glance'@'localhost' i ...