前言
在这里先祝福大家新年快乐!因为假期原因,距离上一篇的生产者教程已经过了很久,今天是上班的第一天,我们继续分享RocketMQ的第四篇教程,主要介绍消费者相关的知识。
一、消费者概述
消费者组:一个逻辑概念,在使用消费者时需要指定一个组名。一个消费者组可以订阅多个Topic。
消费者实例:一个消费者组程序部署了多个进程,每个进程都可以称为一个消费者实例。
订阅关系:一个消费者组订阅一个Topic的某一个 Tag,这种记录被称为订阅关系。
实际使用中,要保证消费订阅关系一致,一个消费者组中的订阅Topic和Tag必须一致。
二、集群消费模式和广播消费模式
1.集群消费模式
在同一个消费组中,消费者是负载均衡的消费Topic中的消息。
设置消费模式为集群模式
consumer.setMessageModel(MessageModel.CLUSTERING);
生产者:
每一秒生产一条数据
//创建DefaultMQProducer消息生产者对象
DefaultMQProducer producer = new DefaultMQProducer("TestProducerGroup");
//设置NameServer
producer.setNamesrvAddr("192.168.2.5:9876");
//设置NameServer节点地址,多个节点间用分号分割
try {
//与NameServer建立长连接
producer.start();
//发送一百条数据
for (int i = 1; i <= 10; i++) {
//1S中发送一次
Thread.sleep(1000);
JSONObject json = new JSONObject();
json.put("orderId", i);
json.put("desc", "这是第" + i + "个订单");
//数据正文
String data = json.toJSONString();
Message message = new Message("TopicOrder", "", data.getBytes());
//发送消息,获取发送结果
SendResult result = producer.send(message);
//将发送结果对象打印在控制台
System.out.println("消息已发送:MsgId:" + result.getMsgId() + ",发送状态:"
+ result.getSendStatus());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
producer.shutdown();
} catch (Exception e) {
}
}
启动两个消费者
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("TestPushConsumerGroup");
try {
//设置NameServer节点
consumer.setNamesrvAddr("192.168.2.5:9876");
//订阅主题
consumer.subscribe("TopicOrder", "*");
//设置消费模式为集群模式
consumer.setMessageModel(MessageModel.CLUSTERING);
//创建监听,当有新的消息监听程序会及时捕捉并加以处理。
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
//批量数据处理
for (MessageExt msg : msgs) {
System.out.println("消费者获取数据:" + msg.getMsgId() + "==>" + new
String(msg.getBody()));
}
//返回数据已接收标识
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者,与Broker建立长连接,开始监听。
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
启动之后我们可以看到两个消费者各自消费了5条数据
2.广播消费模式
消息广播分发,所有消费者组中的消费者消费全量的消息。
唯一不同点就是setMessageModel设置的消费模式不同
//设置消费模式为集群模式
consumer.setMessageModel(MessageModel.BROADCASTING);
设置完后依然启动两个消费者
我们看到,两个消费者获得了相同的数据
三、消费者如何实现消息过滤
1.tag方式
tag方式经过前面的教程,大家也应该了解到如何使用,只需消费者订阅的时候,设置需要消费的tag标记即可
写法:
- * :消费所有消息
- Tag : 只消费制定的Tag
- Tag1 || Tag2 || Tag3 : 只要满足一个Tag值就被消费
2.sql方式(不推荐)
当生产者发送消息的时候,可以自定义消息的额外属性,消费者可以通过类似sql的语法实现消息过滤,相比较Tag方式,会更加灵活
先在broker.conf增加配置
#开启自定义属性SQL过滤
enablePropertyFilter=true
java代码如下:
生产者:测试取余为1的设置type为food
//创建DefaultMQProducer消息生产者对象
DefaultMQProducer producer = new DefaultMQProducer("TestProducerGroup");
//设置NameServer
producer.setNamesrvAddr("192.168.2.5:9876");
//设置NameServer节点地址,多个节点间用分号分割
try {
//与NameServer建立长连接
producer.start();
//发送一百条数据
for (int i = 1; i <= 10; i++) {
//1S中发送一次
Thread.sleep(1000);
JSONObject json = new JSONObject();
json.put("orderId", i);
json.put("desc", "这是第" + i + "个订单");
//数据正文
String data = json.toJSONString();
Message message = new Message("TopicOrder", "", data.getBytes());
if(i % 3 == 1){
message.putUserProperty("type","food");
}else{
message.putUserProperty("type","drink");
}
//发送消息,获取发送结果
SendResult result = producer.send(message);
//将发送结果对象打印在控制台
System.out.println("消息已发送:MsgId:" + result.getMsgId() + ",发送状态:"
+ result.getSendStatus());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
producer.shutdown();
} catch (Exception e) {
}
}
消费者:消费type为food的消息
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("TestPushConsumerGroup");
try {
//设置NameServer节点
consumer.setNamesrvAddr("192.168.2.5:9876");
//订阅主题,消费type为food的消息
consumer.subscribe("TopicOrder", MessageSelector.bySql("type='food'"));
//设置消费模式为集群模式
consumer.setMessageModel(MessageModel.CLUSTERING);
//创建监听,当有新的消息监听程序会及时捕捉并加以处理。
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
//批量数据处理
for (MessageExt msg : msgs) {
System.out.println("消费者获取数据:" + msg.getMsgId() + "==>" + new
String(msg.getBody()));
}
//返回数据已接收标识
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者,与Broker建立长连接,开始监听。
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
结果:
从结果中我们可以看出,该消费者只消费了type为food的消息
四、有序消息的实现
在第三章中,我们已经了解到了生产者如何发送有序消息,实现的方式就是通过数据的hash值,根据hash值判断,指定让消息通过哪一个队列传输,忘记的小伙伴可以回头复习一下,下面将介绍消费者端如何进行有序消费
生产者:
我们生成了20个订单,每个订单需要发送三条消息,顺序是创建订单、生成支付订单、创建物流信息
通过有序消息的发送,我们已经确保相同订单号的数据是通过相同队列来发送消息
//创建DefaultMQProducer消息生产者对象
DefaultMQProducer producer = new DefaultMQProducer("TestProducerGroup");
//设置NameServer
producer.setNamesrvAddr("192.168.2.5:9876");
//设置NameServer节点地址,多个节点间用分号分割
try {
//与NameServer建立长连接
producer.start();
//设置个集合,集合信息中是要发送订单消息、支付消息、物流消息。要保证相同订单号的在同一个队列
List<JSONObject> orderInfos = new ArrayList<>();
//一共10个订单,每一个订单要发送3条消息
for (int i = 1; i <= 10; i++) {
//每一个订单发送的信息
for (int j = 0; j < 3; j++) {
JSONObject orderInfo = new JSONObject();
orderInfo.put("orderId",i);
String msg = "";
switch (j % 3){
case 0:
msg = "订单号:"+i+"创建订单";
break;
case 1:
msg = "订单号:"+i+"生成支付订单";
break;
case 2:
msg = "订单号:"+i+"创建物流信息";
break;
}
orderInfo.put("msg",msg);
Message message = new Message("TopicOrder", "orderinfo", i + "",
orderInfo.toJSONString().getBytes());
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
//附加参数 producer.send方法中的第三个参数
int id = (Integer) arg;
//和队列总数取余,来决定选择哪个队列,因为相同的订单ID取余的结果是一样的,这样就能保证相同的订单,我们选择的是同一个队列
int index = id % mqs.size();
MessageQueue messageQueue = mqs.get(index);
System.out.println("队列:" + messageQueue+",信息:"+new String(msg.getBody()));
return messageQueue;
}
}, i);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
producer.shutdown();
} catch (Exception e) {
}
}
消费者
使用MessageListenerOrderly监听器,用于实现有序消费,相同订单号的消息由同一个消费者进行消费处理
//创建消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("TestPushConsumerGroup");
try {
//设置NameServer节点
consumer.setNamesrvAddr("192.168.2.5:9876");
//订阅主题,
consumer.subscribe("TopicOrder", "");
//设置消费模式为集群模式
consumer.setMessageModel(MessageModel.CLUSTERING);
//创建监听,使用MessageListenerOrderly监听器,用于实现有序队列
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt msg : list) {
System.out.println("队列"+consumeOrderlyContext.getMessageQueue()+"获取数据:" + msg.getMsgId() + "==>" + new
String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
//启动消费者,与Broker建立长连接,开始监听。
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
接下来我们看下生产者的日志记录:
可以看出,相同订单号的订单产生的信息,由同一个队列进行发送
消费者日志:
从日志不难看出,相同订单号的消息,由同一个消费者顺序执行,以上就是有序消息的实现过程
五、注意事项
1.RocketMQ如何保证消息可靠
消费侧通过重试-死信机制,Rebalance机制等多种机制保证消费的可靠性
重试-死信机制
提供了内置的消息重试机制,以确保在消息发送或者消费时发生问题时,消息能够被重新尝试。默认的情况为16次,这16次的尝试机会,间隔的时间也不同
尝试次数 | 与上次重试间隔时间 | 尝试次数 | 与上次重试间隔时间 |
1 | 10s | 9 | 7min |
2 | 30s | 10 | 8min |
3 | 1min | 11 | 9min |
4 | 2min | 12 | 10min |
5 | 3min | 13 | 20min |
6 | 4min | 14 | 30min |
7 | 5min | 15 | 1h |
8 | 6min | 16 | 2h |
死信机制是指被消费者重试次数达到阈值后,无法被消费的信息被投入到了死信队列,死信队列中的消息可以通过一个专门的消费者进行处理,以查看失败的消息并采取适当的措施,比如记录日志、人工干预或者其他处理方式
Rebalance机制
用于动态调整消费者集群中各个消费者实例之间对消息队列的分配,以确保负载均衡。这个机制使得在集群中增加或减少消费者实例时,系统能够自动地重新分配队列,以确保每个消费者实例负责处理的队列数量大致相等。
2.有序消费的使用限制
有序消费模式只支持集群模式MessageModel.CLUSTERING,不支持广播模式,这一点要注意
下一篇将介绍SpringBoot与RocketMQ的集成使用
关注我,不迷路