文章目录
1.项目准备
1.1 项目工程构建
新建一个新的Empty Project
命名为rocketmq-dtx
并在工程下创建Maven
的新的Module
命名为rocketmq-api
:
修改rocketmq-api
下的pom文件,添加 rocketmq-client 依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.study</groupId>
<artifactId>rocketmq-api</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-store</artifactId>
<version>4.7.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.2 消息队列创建
访问RocketMQ管理界面,创建Topic队列:
2.同步消息
同步消息发送要保证强一致性,发到master主服务器的消息需要通过slave从服务器复制后,才会向生产者发送反馈信息。
生产者需要等待主从复制完成后,才可以接收到反馈消息,相对的,还有一个异步消息,异步消息中,生产者发送消息成功,立刻就会获得发送成功的反馈,生产者即可执行后续步骤,从服务器慢慢进行复制即可,这种异步消息效率更高;
这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知。
2.1 生产者
package m1;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.Scanner;
/**
*发送同步消息
*/
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
//新建生产者对象
DefaultMQProducer p = new DefaultMQProducer("p1");
//设置 name service 地址
p.setNamesrvAddr("192.168.64.161:9876");
//启动(连接服务器)
p.start();
//主题相当于是消息的分类, 一类消息使用一个主题,Topic1是我们自己在服务器创建的队列,其相当于1级分类
String topic = "Topic1";
//tag 相当于是消息的二级分类, 在一个主题下, 可以通过 tag 再对消息进行分类,名字可以自定义,叫什么都行,相当于2级分类
String tag = "Tag1";
while (true){
System.out.println("请输入消息:");
String s = new Scanner(System.in).nextLine();
//用Message 对象封装消息及发送的消息位置(使用apache的包)
Message msg = new Message(topic, tag, s.getBytes());
//发送
SendResult r = p.send(msg);
System.out.println(r);
}
}
}
此时我们运行生产者发送消息可以在控制台打印出我们发送的消息内容:
在messageQueue
消息队列的值中我们可以看到,当前发送消息的Topic和queueId以及队列下标分别是多少;
并且我们可以在管理界面可以看到,我们一共发送了6条消息:
2.2 消费者
消费者的要点:
- push 和 pull
- 消费者有两种模式:push 和 pull。
- push 模式由服务器主动向消费者发送消息;pull 模式由消费者主动向服务器请求消息。
- 在消费者处理能力有限时,为了减轻消费者的压力,可以采用pull模式,当其中一个消费者处理不过来时,可以由其他消费者进行请求。多数情况下都采用 push模式,push模式只进行一次分发,处理效率更高。
- NameServer
- 消费者需要向 NameServer 询问 Topic 的路由信息。
- Topic
- 从指定的Topic接收消息。Topic相当于是一级分类。
- Tag
- Topic 相当于是一级分类,Tag 相当于是2级分类。
- 多个 Tag 可以这样写:
TagA || TagB || TagC
- 不指定 Tag,或者说接收所有的 Tag,可以写星号:
*
package m1;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
public class Consumer {
public static void main(String[] args) throws MQClientException {
//新建消费者对象
DefaultMQPushConsumer c = new DefaultMQPushConsumer("c1");
//设置name service
c.setNamesrvAddr("192.168.64.161:9876");
//启动前需要设置从哪里接收(订阅)消息
//从哪接收消息
String topic = "Topic1";
//第二个参数:"*"所有消息,“Tag1||Tag2||Tag3"多种标签
String tag = "Tag1";
c.subscribe(topic,tag);
//添加处理消息的监听器
// c.setMessageListener(new MessageListenerConcurrently() {
// @Override
// public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// return null;
// }
// });
//我们这里使用lamda表达式简写
c.setMessageListener((MessageListenerConcurrently) (msgs, consumeConcurrentlyContext) -> {
for (MessageExt ext : msgs) {
String s = new String(ext.getBody());
System.out.println("收到的消息为:"+s);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
//如果处理失败,要求服务器重新发送此消息,稍后重新消费
//return ConsumeConcurrentlyStatus.RECONSUME_LATER;
});
//启动(连接服务器)
c.start();
System.out.println("开始消费数据!");
}
}
我们运行消费者类测试消费成功的情况,运行后可以在控制台看到此前生产者发送的6条消息,不过其接收顺序与发送顺序并不是一致的:
但是当返回的处理结果为失败时,服务器会按照延时消息的时间表,最多发送18次消息给消费者,我们注释掉返回成功的代码,打开返回失败的注释重启项目并使用生产者重新发送一条消息:
3.延时消息
消息发送到 Rocketmq 服务器后, 生产者会延迟一定时间再向消费者进行投递,当消费者接收消息返回失败的情况下,延时消息会自动启动,按照延时级别自动发送最多18次(全部失败的情况下)消息给消费者,当然我们也可以自己调用延时消息。
延时消息的使用场景:
- 订单长时间不付款取消订单释放库存
- 某些消息长时间未确认转至默认页面
3.1 延时级别
比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。
生产者发送消息时,对消息进行延时设置:
msg.setDelayTimeLevel(3);
其中 3
代表级别而不是一个具体的时间值,级别和延时时长对应关系是在 MessageStoreConfig
类种进行定义的:
this.messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
对应关系表:
级别 | 延时时长 |
---|---|
1 | 1s |
2 | 5s |
3 | 10s |
4 | 30s |
5 | 1m |
6 | 2m |
7 | 3m |
8 | 4m |
9 | 5m |
10 | 6m |
11 | 7m |
12 | 8m |
13 | 9m |
14 | 10m |
15 | 20m |
16 | 30m |
17 | 1h |
18 | 2h |
3.2 测试
我们将m1
包复制一份改名为m2`包,并在生产中添加延时消息机制并设置级别为3:
然后启动生产者与消费中,并在生产者中发送多条消息后,我们可以观察到,消费者要经过10s才能接收到消息;
4.顺序消息
Concurrently
监听器会启动多个线程,并发的接收处理多条消息,所以即使是一个消费者也可以同时接收多条消息进行处理,但是这种情况下,所处理的消息有快有慢,不能保证处理顺序,就导致某些场景下,无法保障业务的正确执行,这时就需要使用顺序消息;
上图演示了 Rocketmq 顺序消息的基本原理:
- 同一组有序的消息序列,会被发送到同一个队列,按照 FIFO 的方式进行处理
- 一个队列只允许一个消费者线程接收消息,这样就保证消息按顺序被接收
下面以订单为例:
一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中。消费时,从同一个队列接收同一个订单的消息。
我们通过添加队列选择器首先来选择一个队列,通过选择将数据ID相同的消息(通常选择将数据ID进行取余来进行判断)发送到同一个队列来进行处理,这样来保障消息的顺序执行;
具体的实现我们需要保证:
- 生产者把一组消息发送到同一个队列
- 消费者只用一个线程来接收、处理消息
4.1 生产者
package m3;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
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 org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.List;
import java.util.Scanner;
/*
以下消息, 相同id的消息按顺序发送到同一个队列,
消费时也从同一个队列按顺序消费
topic
======================= queue1
======================= queue2
111,消息1 111,消息2 111,消息3 ------->======================= queue3
======================= queue4
222,消息1 222,消息2 222,消息3 ------->======================= queue5
======================= queue6
333,消息1 333,消息2 333,消息3 ------->======================= queue7
======================= queue8
......
*/
public class Producer {
//将要发送的消息
static String[] msgs = {
"15103111039,创建",
"15103111065,创建",
"15103111039,付款",
"15103117235,创建",
"15103111065,付款",
"15103117235,付款",
"15103111065,完成",
"15103111039,推送",
"15103117235,完成",
"15103111039,完成"
};
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
//新建生产者
DefaultMQProducer p = new DefaultMQProducer("p2");
//设置name service
p.setNamesrvAddr("192.168.64.161:9876");
//启动
p.start();
//遍历数组,选择队列进行发送
String topic = "Topic2";
String tag = "Tag1";
for (String s : msgs) {
System.out.println("按回车发送此消息: "+s);
new Scanner(System.in).nextLine();
//服务器会自动创建Topic2
Message msg = new Message(topic, tag, s.getBytes());
//通过”,“号将ID进行拆分
String[] a = s.split(",");
long orderId = Long.valueOf(a[0]);
/*
send(msg, queueSelector, obj)
queueSelector是队列选择器
第三个参数会传递到queueSelector, 作为它的选择依据,这里用订单的id对队列数量取余来计算队列索引
*/
SendResult r = p.send(msg, new MessageQueueSelector() {
/*
三个参数的含义:
queueList: 当前Topic服务器中所有队列的列表
message: 消息对象,send()方法传入的消息对象也会被传入到这里
o: 队列ID,send()方法传入的orderId也会被传入到这里
*/
@Override
public MessageQueue select(List<MessageQueue> queueList, Message message, Object o) {
Long orderId = (Long) o;
//订单id对队列数量取余, 相同订单id得到相同的队列索引
long index = orderId % queueList.size();
System.out.println("消息已发送到: "+queueList.get((int) index));
return queueList.get((int) index);
}
}, orderId);
//观察其发送到了哪个队列
System.out.println(r.getMessageQueue());
}
}
}
4.2 消费者
package m3;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class Consumer {
public static void main(String[] args) throws MQClientException {
//新建消费者
DefaultMQPushConsumer c = new DefaultMQPushConsumer("c2");
//设置name service
c.setNamesrvAddr("192.168.64.161:9876");
//从哪里订阅消息
c.subscribe("Topic2", "*");
//设置监听器 Orderly
c.setMessageListener(((MessageListenerOrderly) (list, consumeOrderlyContext) -> {
String t = Thread.currentThread().getName();
for (MessageExt msg : list) {
String s = new String(msg.getBody());
System.out.println("收到:"+s);
}
return ConsumeOrderlyStatus.SUCCESS;
}
));
//启动
c.start();
}
}
完成后我们运行生产者和消费者可以看到,在消费者中没一组消息都是固定的先后顺序去处理的:
5. 事务消息
RocketMQ 提供了可靠性消息,也叫事务消息。下面分析一下其原理。
5.1 事务消息的原理
5.2 “可靠消息”发送
下面来看 RocketMQ 的事务消息是如何来发送“可靠消息”的,只需要以下三步:
- 发送半消息(半消息不会发送给消费者)
- 执行本地事务
- 提交消息
完成事务消息发送后,消费者就可以以正常的方式来消费数据。
RocketMQ 的自动重发机制在绝大多数情况下,都可以保证消息被正确消费。
假如消息最终消费失败了,会重复兜底发送18次,如果18次后消费还是失败,那么最终消息会变为死信消息,还可以由人工处理进行托底在管理后台进行处理。
5.3 错误发送“可靠消息”
上面分析的是正常情况下的执行流程。下面再来看两种错误情况:
- 事务执行失败时回滚消息
- 服务器无法得知消息状态时,需要主动回查消息状态
5.3.1 回滚:
5.3.2 消息回查:
5.4 生产者
package m4;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.Scanner;
/**
* 事务消息
* */
public class Producer {
public static void main(String[] args) throws MQClientException {
//新建事务消息生产者
TransactionMQProducer p = new TransactionMQProducer("p3");
//设置name service
p.setNamesrvAddr("192.168.64.161:9876");
//设置事务消息监听器
p.setTransactionListener(new TransactionListener() {
//在这里执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
//参数:消息对象,业务数据参数
System.out.println("发送事务半消息后,触发执行本地事务;");
System.out.println("业务数据参数为:"+o);
//模拟本地事务执行情况
if(Math.random() < 0.5){
//执行成功,返回提交状态,Rocketmq向消费者投递消息
System.out.println("执行本地事务成功!");
return LocalTransactionState.COMMIT_MESSAGE;
}else {
//执行失败,返回回滚状态,撤销消息
System.out.println("执行本地事务失败!");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
/*
* 回查方法:处理Rocketmq的事务状态回查
* 检测频率默认1分钟,可通过在broker.conf文件中设置transactionCheckInterval的值来改变默认值,单位为毫秒。
* 在真实的业务案例中,我们需要在执行本地事务后,将执行状态保存到本地后,由回查方法查询执行状态为UNKNOW时执行此方法
* 但是我们这里只是简单模拟,所以不进行状态保存
* */
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("Rocketmq正在回查事务状态");
return LocalTransactionState.UNKNOW;
}
});
//启动
p.start();
//发送事务消息,触发事务消息监听器来执行本地事务
while (true){
System.out.println("输入消息:");
String s = new Scanner(System.in).nextLine();
Message msg = new Message("Topic3", s.getBytes());
//发送事务消息,触发事务消息监听器来执行本地事务
p.sendMessageInTransaction(msg,"第2个参数为传递到监听器的业务数据参数,因为我们这里是简单测试,没有业务数据,所以我们传递一个字符串");
System.out.println("事务消息成功发送");
}
}
}
我们首先不考虑回查方法,将回查方法的代码和模拟网络失败的代码进行注释后运行生产者,我们测试消息发送,输入消息发送直到出现本地事务失败的提示;
此时我们可以在管理界面中查看到我们发送消息的情况,其事务监听有自己默认的队列:
然后再打开回查方法和模拟网络失败的代码,此时重新执行并发送一次消息后等待回查代码的自动运行:
5.5 消费者
package m4;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
/**
* 事务消息
* */
public class Consumer {
public static void main(String[] args) throws MQClientException {
//新建消费者对象
DefaultMQPushConsumer c = new DefaultMQPushConsumer("c4");
//设置name service
c.setNamesrvAddr("192.168.64.161:9876");
//启动前需要设置从哪里接收(订阅)消息
//从哪接收消息
String topic = "Topic1";
//第二个参数:"*"所有消息,“Tag1||Tag2||Tag3"多种标签
c.subscribe(topic,"*");
//添加处理消息的监听器
c.setMessageListener((MessageListenerConcurrently) (msgs, consumeConcurrentlyContext) -> {
for (MessageExt ext : msgs) {
String s = new String(ext.getBody());
System.out.println("收到的消息为:"+s);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
//如果处理失败,要求服务器重新发送此消息,稍后重新消费,服务器最多会有18次发送,每次发送的时间间隔会越来越长(具体查看延时消息)
//return ConsumeConcurrentlyStatus.RECONSUME_LATER;
});
//启动(连接服务器)
c.start();
System.out.println("开始消费数据!");
}
}
6.异步消息
master 收到消息后立即向生产者进行反馈。之后再以异步方式向 slave 复制消息。
异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。异步消息中,生产者发送消息成功,立刻就会获得发送成功的反馈,生产者即可执行后续步骤,从服务器慢慢进行复制即可,这种异步消息效率更高;
6.1 生产者
package m5;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.Scanner;
/*
异步发送消息
一条消息送出后, 不必暂停等待服务器针对这条消息的反馈, 而是可以立即发送后续消息.
使用监听器, 以异步的方式接收服务器的反馈
*/
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException {
DefaultMQProducer p = new DefaultMQProducer("producer-demo2");
p.setNamesrvAddr("**192.168.64.161:9876**");
p.start();
p.setRetryTimesWhenSendAsyncFailed(0);
String topic = "Topic2";
String tag = "TagA";
String key = "Key-demo2";
while (true) {
System.out.print("输入消息,用逗号分隔多条消息: ");
String[] a = new Scanner(System.in).nextLine().split(",");
for (String s : a) {
Message msg = new Message(topic, tag, key, s.getBytes());
p.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("\n\n消息发送成功 : "+sendResult);
}
@Override
public void onException(Throwable throwable) {
System.out.println("\n\n消息发送失败");
}
});
System.out.println("--------------------消息已送出-----------------------");
}
}
}
}
6.2 消费者
package m5;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/*
与 demo1.Consumer 完全相同
*/
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer c = new DefaultMQPushConsumer("consumer-demo2");
c.setNamesrvAddr("192.168.64.161:9876");
c.subscribe("Topic2", "TagA");
c.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()) + " - " + msg);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
c.start();
System.out.println("开始消费数据");
}
}
7.单向消息
这种方式主要用在不特别关心发送结果的场景,例如日志发送。
7.1 生产者
package m6;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.Scanner;
/*
单向消息
消息发出后, 服务器不会返回结果
*/
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException {
DefaultMQProducer p = new DefaultMQProducer("producer-demo3");
p.setNamesrvAddr("192.168.64.161:9876");
p.start();
String topic = "Topic3";
String tag = "TagA";
while (true) {
System.out.print("输入消息,用逗号分隔多条消息: ");
String[] a = new Scanner(System.in).nextLine().split(",");
for (String s : a) {
Message msg = new Message(topic, tag, s.getBytes());
p.sendOneway(msg);
}
System.out.println("--------------------消息已送出-----------------------");
}
}
}
7.2 消费者
package m6;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/*
与 demo1.Consumer 完全相同
*/
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer c = new DefaultMQPushConsumer("consumer-demo2");
c.setNamesrvAddr("192.168.64.161:9876");
c.subscribe("Topic3", "TagA");
c.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()) + " - " + msg);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
c.start();
System.out.println("开始消费数据");
}
}
8.批量消息
批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB。
8.1 生产者
package m7;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.ArrayList;
import java.util.Scanner;
/*
批量发送消息能显著提高传递小消息的性能。限制是:
- 这些批量消息应该有相同的topic,
- 相同的waitStoreMsgOK,
- 而且不能是延时消息。
- 这一批消息的总大小不应超过4MB。
如果超出4M需要进行数据分割, 请参考官方代码样例https://github.com/apache/rocketmq/blob/master/docs/cn/RocketMQ_Example.md
*/
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer p = new DefaultMQProducer("producer-demo6");
p.setNamesrvAddr("192.168.64.161:9876");
p.start();
String topic = "Topic6";
while (true) {
System.out.print("输入消息,用逗号分隔多条消息: ");
String[] a = new Scanner(System.in).nextLine().split(",");
ArrayList<Message> messages = new ArrayList<>();
for (String s : a) {
messages.add(new Message(topic, s.getBytes()));
}
p.send(messages);
System.out.println("批量消息已发送");
}
}
}
8.2 消费者
package m7;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer c = new DefaultMQPushConsumer("consumer-demo6");
c.setNamesrvAddr("192.168.64.161:9876");
c.subscribe("Topic6", "*");
c.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg :
list) {
System.out.println("收到: "+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
c.start();
System.out.println("开始消费数据");
}
}
9.消息过滤
9.1 Tag 过滤
Tag 可以满足大多数消息过滤的需求。使用 Tag 过滤非常简单,例如:
consumer.subscribe("Topic1", "TagA || TagB || TagC");
9.2 对自定义属性过滤
生产者可以在消息中添加自定义的属性:
msg.putUserProperty("prop1", "1");
msg.putUserProperty("prop2", "2");
消费者接收数据时,可以根据属性来过滤消息:
consumer.subscribe("Topic7", MessageSelector.bySql("prop1=1 or prop2=2"));
可以看到,自定义属性的过滤语法是 Sql 语法,RocketMQ只定义了一些基本语法来支持这个特性,支持的 Sql 过滤语法如下:
- 数值比较,比如:>,>=,<,<=,BETWEEN,=;
- 字符比较,比如:=,<>,IN;
IS NULL
或者IS NOT NULL
;- 逻辑符号 AND,OR,NOT;
9.3 生产者
package m8;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.Random;
import java.util.Scanner;
/*
发送的消息中包含 tag 和 userProperty
消费者接收时,可以选择用 tag 或 userProperty 进行过滤
*/
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer p = new DefaultMQProducer("producer-demo7");
p.setNamesrvAddr("192.168.64.161:9876");
p.start();
String topic = "Topic7";
while (true) {
System.out.print("输入消息,用逗号分隔多条消息: ");
String[] a = new Scanner(System.in).nextLine().split(",");
System.out.print("输入Tag: ");
String tag = new Scanner(System.in).nextLine();
for (String s : a) {
Message msg = new Message(topic, tag, s.getBytes());
msg.putUserProperty("rnd", ""+new Random().nextInt(4));
p.send(msg);
}
}
}
}
9.4 消费者
package m8;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
import java.util.Scanner;
/*
如果使用sql过滤,需要在 broker.properties 中添加配置来启用 sql 过滤:
enablePropertyFilter=true
*/
public class Consumer {
public static void main(String[] args) throws MQClientException {
System.out.print("使用Tag过滤还是使用Sql过滤(tag/sql): ");
String ts = new Scanner(System.in).nextLine();
DefaultMQPushConsumer c = new DefaultMQPushConsumer("consumer-demo7");
c.setNamesrvAddr("192.168.64.161:9876");
if (ts.equalsIgnoreCase("tag")) {
System.out.println("使用Tag过滤: TagA || TagB || TagC");
c.subscribe("Topic7", "TagA || TagB || TagC");
} else {
System.out.println("使用Sql过滤: rnd=1 or rnd > 2");
c.subscribe("Topic7", MessageSelector.bySql("rnd=1 or rnd > 2"));
}
c.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg : list) {
System.out.println(new String(msg.getBody()) + " - " + msg.getUserProperty("rnd"));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
c.start();
System.out.println("开始消费数据");
}
}