1. 消息发送方式
1.1 同步消息
消息发送进入同步等待状态,保证消息一定到达
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("floweryu");
// 设置nameserver地址
defaultMQProducer.setNamesrvAddr("localhost:9876");
defaultMQProducer.start();
// topic 消息将要发送的地址
// body 消息中具体的数据
Message msg1 = new Message("myTopic", "hello floweryu 01".getBytes());
Message msg2 = new Message("myTopic", "hello floweryu 02".getBytes());
Message msg3 = new Message("myTopic", "hello floweryu 03".getBytes());
ArrayList<Message> list = new ArrayList<>();
list.add(msg1);
list.add(msg2);
list.add(msg3);
SendResult send = defaultMQProducer.send(list);
System.out.println("sentResult: " + send);
defaultMQProducer.shutdown();
System.out.println("producer has stop !");
}
}
1.2 异步消息
public class ProducerAsync {
public static void main(String[] args) throws Exception {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("floweryu");
// 设置nameserver地址
defaultMQProducer.setNamesrvAddr("localhost:9876");
defaultMQProducer.start();
// 异步可靠消息
// 不会阻塞 等待 broker 确认 采用事件监听的方式接受broker确认
Message msg = new Message("myTopic", "hello floweryu async".getBytes());
defaultMQProducer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("message success");
System.out.println("send result" + sendResult);
}
@Override
public void onException(Throwable throwable) {
// 发生异常
throwable.printStackTrace();
System.out.println("catch error");
}
});
}
}
1.3 单向消息
只发消息,不等待服务器响应。
通常消息的发送是这样一个过程:
- 客户端发送请求到服务器
- 服务器处理请求
- 服务器向客户端返回应答
所以,一次消息发送的耗时时间是上述三个步骤的总和,而某些场景要求耗时非常短,但是对可靠性要求并不高,例如日志收集类应用,此类应用可以采用oneway形式调用,oneway形式只发送请求不等待应答,而发送请求在客户端实现层面仅仅是一个操作系统系统调用的开销,即将数据写入客户端的socket缓冲区,此过程耗时通常在微秒级。
public class ProduceOneWay {
public static void main(String[] args) throws Exception {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("floweryu");
// 设置nameserver地址
defaultMQProducer.setNamesrvAddr("106.15.42.148:9876");
defaultMQProducer.start();
Message msg = new Message("myTopic", "hello floweryu one way".getBytes());
defaultMQProducer.sendOneway(msg);
defaultMQProducer.shutdown();
System.out.println("shutdown...");
}
}
2. 消息消费模式
#集群消费
当使用集群消费模式时,消息队列RocketMQ版认为任意一条消息只需要被集群内的任意一个消费者处理即可。
适用场景:
适用于消费端集群化部署,每条消息只需要被处理一次的场景。此外,由于消费进度在服务端维护,可靠性更高。
注意事项:
- 集群消费模式下,每一条消息都只会被分发到一台机器上处理。如果需要被集群下的每一台机器都处理,请使用广播模式。
- 集群消费模式下,不保证每一次失败重投的消息路由到同一台机器上。
广播消费
当使用广播消费模式时,消息队列RocketMQ版会将每条消息推送给集群内所有注册过的消费者,保证消息至少被每个消费者消费一次。
适用场景:
适用于消费端集群化部署,每条消息需要被集群下的每个消费者处理的场景。具体消费示例如下图所示。
注意事项:
- 广播消费模式下不支持顺序消息。
- 广播消费模式下不支持重置消费位点。
- 每条消息都需要被相同订阅逻辑的多台机器处理。
- 消费进度在客户端维护,出现重复消费的概率稍大于集群模式。
- 广播模式下,消息队列RocketMQ版保证每条消息至少被每台客户端消费一次,但是并不会重投消费失败的消息,因此业务方需要关注消费失败的情况。
- 广播模式下,客户端每一次重启都会从最新消息消费。客户端在被停止期间发送至服务端的消息将会被自动跳过,请谨慎选择。
- 广播模式下,每条消息都会被大量的客户端重复处理,因此推荐尽可能使用集群模式。
- 广播模式下服务端不维护消费进度,所以消息队列RocketMQ版控制台不支持消息堆积查询、消息堆积报警和订阅关系查询功能。
3. 消息过滤
在服务端的conf/broker.conf中添加enableProperyFilter=true启动过滤。
下面是为每个消息添加了age属性,然后通过age对消息进行过滤。
消费者代码:
public class Consumer {
public static void main(String[] args) throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("floweryu_consumer");
consumer.setNamesrvAddr("localhost:9876");
// 每一个 consumer 关注一个topic
// 使用属性age进行过滤
MessageSelector messageSelector = MessageSelector.bySql("age >= 18 and age <= 28");
consumer.subscribe("myTopic01", messageSelector);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()));
}
// 默认情况下 这条消息只会被一个consumer消费 点对点
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer TAG-B start...");
}
}
/** 接收到的消息:
hello floweryu TagA18
hello floweryu TagA19
hello floweryu TagA20
hello floweryu TagA21
hello floweryu TagA27
hello floweryu TagA28
hello floweryu TagA22
hello floweryu TagA24
hello floweryu TagA25
hello floweryu TagA23
hello floweryu TagA26
*/
生产者代码:
public class ProducerTags {
public static void main(String[] args) throws Exception {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("floweryu");
// 设置nameserver地址
defaultMQProducer.setNamesrvAddr("localhost:9876");
defaultMQProducer.start();
// 异步可靠消息
// 不会阻塞 等待 broker 确认 采用事件监听的方式接受broker确认
ArrayList<Message> list = new ArrayList<>();
for (int i = 0; i <= 100; i++) {
Message msg = new Message("myTopic01", "TAG-B", "KEY-A", ("hello floweryu TagA" + i).getBytes());
// 为每条消息添加age属性
msg.putUserProperty("age", String.valueOf(i));
list.add(msg);
}
defaultMQProducer.send(list);
defaultMQProducer.shutdown();
System.out.println("shutdown...");
}
}
4. 事务消息
事务消息的回调有什么用checkLocalTransaction?
public class Producer {
public static void main(String[] args) throws Exception {
TransactionMQProducer transactionMQProducer = new TransactionMQProducer("floweryu");
transactionMQProducer.setNamesrvAddr("localhost:9876");
transactionMQProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
// 执行本地事务
System.out.println("------执行本地事务------");
System.out.println("msg: " + Arrays.toString(message.getBody()));
System.out.println("msg: " + message.getTransactionId());
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("------检查本地事务------");
System.out.println("msg: " + Arrays.toString(messageExt.getBody()));
System.out.println("msg: " + messageExt.getTransactionId());
// broker 回调,检查事务
return LocalTransactionState.UNKNOW;
}
});
transactionMQProducer.start();
SendResult sendResult = transactionMQProducer.sendMessageInTransaction(new Message("topic002", "测试事务消息".getBytes()), null);
System.out.println("sendResult: " + sendResult);
// transactionMQProducer.shutdown();
System.out.println("服务已关闭");
}
}
/**
------执行本地事务------
msg: [-26, -75, -117, -24, -81, -107, -28, -70, -117, -27, -118, -95, -26, -74, -120, -26, -127, -81]
msg: 7F0000012F5418B4AAC218E5F78F0000
sendResult: SendResult [sendStatus=SEND_OK, msgId=7F0000012F5418B4AAC218E5F78F0000, offsetMsgId=null, messageQueue=MessageQueue [topic=topic003, brokerName=broker-a, queueId=3], queueOffset=24]
服务已关闭
------检查本地事务------
msg: [-26, -75, -117, -24, -81, -107, -28, -70, -117, -27, -118, -95, -26, -74, -120, -26, -127, -81]
msg: 7F0000012F5418B4AAC218E5F78F0000
*/
5. 顺序消息
- 消息属于同一个Topic
- 消息在同一个queue中
- 发消息的时候一个线程发送消息
- 消费的时候一个线程消费一个queue里面的消息
- 多个queue只能保证单个queue里面的顺序
package com.floweryu.rocketmq;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import java.util.List;
/**
* @author floweryu
* @date 2021/8/6 10:49
*/
public class ProducerQueue {
public static void main(String[] args) throws Exception {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("floweryu");
// 设置nameserver地址
defaultMQProducer.setNamesrvAddr("106.15.42.148:9876");
defaultMQProducer.start();
for (int i = 0; i < 20; i++) {
Message msg1 = new Message("orderTopic", ("hello floweryu " + i).getBytes());
SendResult send = defaultMQProducer.send(msg1, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
return list.get(0);
}
}, 0);
System.out.println("sentResult: " + send);
}
defaultMQProducer.shutdown();
System.out.println("producer has stop !");
}
}
package com.floweryu.rocketmq;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import java.util.List;
/**
* @author Floweryu
* @date 2021/8/1 15:51
**/
public class Consumer {
public static void main(String[] args) throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("floweryu_consumer");
consumer.setNamesrvAddr("106.15.42.148:9876");
consumer.subscribe("orderTopic", "*");
consumer.setConsumeThreadMax(1);
consumer.setConsumeThreadMin(1);
// 顺序消费
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("Consumer start...");
}
}