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

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

下面看一下producer的代码:

package com.alibaba.rocketmq.example.message.order;

import com.alibaba.rocketmq.client.exception.MQBrokerException;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.MessageQueueSelector;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;
import com.alibaba.rocketmq.common.message.MessageQueue;
import com.alibaba.rocketmq.remoting.exception.RemotingException; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List; /**
* @author : Jixiaohu
* @Date : 2018-04-19.
* @Time : 9:20.
* @Description :
*/
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException, MQBrokerException {
String groupName = "order_producer";
DefaultMQProducer producer = new DefaultMQProducer(groupName);
producer.setNamesrvAddr("192.168.1.114:9876;192.168.2.2:9876");
producer.start(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(new Date());
try {
for (int i = 1; i <= 3; i++) {
String body = dateStr + "Hello RoctetMq : " + i;
Message msg = new Message("Topic1", "Tag1", "Key" + i,
body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer id = (Integer) o;
return list.get(id);
}
}, 0); //0是队列的下标
System.out.println(sendResult);
}
for (int i = 1; i <= 3; i++) {
String body = dateStr + "Hello RoctetMq : " + i;
Message msg = new Message("Topic1", "Tag1", "Key" + i,
body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer id = (Integer) o;
return list.get(id);
}
}, 1); //1是队列的下标
System.out.println(sendResult);
}
for (int i = 1; i <= 3; i++) {
String body = dateStr + "Hello RoctetMq : " + i;
Message msg = new Message("Topic1", "Tag1", "Key" + i,
body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer id = (Integer) o;
return list.get(id);
}
}, 2); //2是队列的下标
System.out.println(sendResult);
}
for (int i = 1; i <= 3; i++) {
String body = dateStr + "Hello RoctetMq : " + i;
Message msg = new Message("Topic1", "Tag1", "Key" + i,
body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer id = (Integer) o;
return list.get(id);
}
}, 3); //3是队列的下标
System.out.println(sendResult);
}
for (int i = 1; i <= 3; i++) {
String body = dateStr + "Hello RoctetMq : " + i;
Message msg = new Message("Topic1", "Tag1", "Key" + i,
body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer id = (Integer) o;
return list.get(id);
}
}, 4); //4是队列的下标
System.out.println(sendResult);
}
for (int i = 1; i <= 3; i++) {
String body = dateStr + "Hello RoctetMq : " + i;
Message msg = new Message("Topic1", "Tag1", "Key" + i,
body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Integer id = (Integer) o;
return list.get(id);
}
}, 5); //5是队列的下标
System.out.println(sendResult);
}
} catch (RemotingException e) {
e.printStackTrace();
Thread.sleep(1000);
}
producer.shutdown();
}
}

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

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

package com.alibaba.rocketmq.example.message.order;

import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerOrderly;
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
import com.alibaba.rocketmq.common.message.MessageExt; import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit; /**
* @author : Jixiaohu
* @Date : 2018-04-23.
* @Time : 9:35.
* @Description : 顺序消息消费
*/
public class Consumer1 { public Consumer1() throws Exception {
String groupName = "order_producer";
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr("192.168.1.114:9876;192.168.2.2:9876");
/**
* 设置Consumer第一次启动是从队列头开始消费还是队列尾开始消费
* 非第一次启动,那么按照上次消费的位置继续消费
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//订阅的主题,以及过滤的标签内容
consumer.subscribe("Topic1", "*");
//注册监听
consumer.registerMessageListener(new Listener());
consumer.start();
System.out.println("Consumer1 Started.");
} class Listener implements MessageListenerOrderly { private Random random = new Random(); @Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext context) {
// 设置自动提交
context.setAutoCommit(true);
for (MessageExt msg : list) {
System.out.println(msg + ",context" + new String(msg.getBody()));
}
try {
TimeUnit.SECONDS.sleep(random.nextInt(1));
} catch (InterruptedException e) {
e.printStackTrace();
} return ConsumeOrderlyStatus.SUCCESS;
}
} public static void main(String[] args) throws Exception {
new Consumer1();
} }

还是按照先启动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修改成多线程的模式,再次查看一下运行的结果:

package com.alibaba.rocketmq.example.message.thread;

import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
import com.alibaba.rocketmq.common.message.MessageExt; import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit; /**
* @author : Jixiaohu
* @Date : 2018-04-23.
* @Time : 9:35.
* @Description : 顺序消息消费
*/
public class Consumer { public Consumer() throws Exception {
String groupName = "order_producer";
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr("192.168.1.114:9876;192.168.2.2:9876");
/**
* 设置Consumer第一次启动是从队列头开始消费还是队列尾开始消费
* 非第一次启动,那么按照上次消费的位置继续消费
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); //最小线程数
consumer.setConsumeThreadMin(10); //最大线程数
consumer.setConsumeThreadMax(20); //订阅的主题,以及过滤的标签内容
consumer.subscribe("Topic1", "*");
//注册监听
consumer.registerMessageListener(new Listener());
consumer.start();
System.out.println("Consumer Started.");
} class Listener implements MessageListenerConcurrently { private Random random = new Random(); @Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) { for (MessageExt msg : list) {
System.out.println(msg + ",context" + new String(msg.getBody()));
}
try {
TimeUnit.SECONDS.sleep(random.nextInt(1));
} catch (InterruptedException e) {
e.printStackTrace();
} return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
} public static void main(String[] args) throws Exception {
new Consumer();
} }

同样先启动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. 深度学习原理与框架-RNN网络架构-RNN网络 1.RNN的前向传播 2.RNN的反向传播

    对于神经网络而言,每一个样本的输入与输入直接都是独立的,即预测的结果之间并没有联系 而对于RNN而言:不仅仅是有当前的输入,而且上一层的隐藏层也将进行输入,用于进行结果的预测.因此每一个输入都与之前的 ...

  2. 使用原生js实现前端分页功能

    背景: 从后台提取出来数据,在前端进行分页. 代码: user-manage.js window.onload = function(){ var result = { message : " ...

  3. [原]vue - webapp 返回无效 解决方案

  4. 盒子变形-盒子加padding后 变形问题,

    1. box-sizing: content-box: 影响: 加了内外边距后整个盒子的大小同步改变 2.box-sizing: border-box;影响:加了边距后整个盒子大小不受影响 boots ...

  5. EventBus 源码学习

    打开一看,原来相关代码并不多,下面看下细节 主要方法也就是注册,取消注册和发送事件,可以看到两个主要的变量就是subscribers和dispatcher public void register(O ...

  6. JSP基本_JSPの構成要素、アクション、ディレクティブ

    1.JSPの構成要素[コア要素] JSP文法のコアとなる要素で.サーブレットソースに変換される. ・宣言: <%! - %> (宣言で指定した変数は.Javaの「フィールド変数」になる.ス ...

  7. 3:while、for 循环语句

    循环就是重复的做一件事情.python 中的循环语句有 while 和 for. while 循环 while 循环必须得有一个计数器,否则会变成一个死循环. # 例如这段代码,这段程序运行之后会一直 ...

  8. html页面跳转

    button <button onclick="window.location.href='edit.jsp'">完善个人信息</button> 单击提交表 ...

  9. NGUI 背景图自适应

    背景图UISprite组件调整如下: UIRoot设置: 不保持比例自适应: 保持宽与屏幕宽一致,高度随宽的缩放比例进行缩放:

  10. oracle理解和导入导出

    搞过sql server的程序员很难理解oracle的表空间.我在这里简单说一下吧, oracle中的表空间就相当于sql server中的实例,用户就相当于sql server中的库. 所以在ora ...