RocketMQ消息发送样例【五】
一、基本样例
消息发送
1)发送同步消息、
这种可以性同步地发送方式使用的比较广泛,比如:重要消息通知,短信通知
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
// 2.指定Nameserver地址
producer.setNamesrvAddr("192.168.0.123:9876;192.168.0.22:9876");
// 3.启动producer
producer.start();
for(int i=0; i< 10; i++){
//创建消息并指定topic,tag消息体
Message msg = new Message("TopicTest",
"TagA",
("hello RocketMQ - " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
//5.发送消息
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", "发送消息状态" + sendResult);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
2)发送异步消息
异步发送消息通常用在对响应时间铭感的业务场景, 即发送端不能容忍时间地等待Broker的响应。
public class AsyncProducer {
public static void main(String[] args) throws Exception{
//实例化消息生产者
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
//设置nameserver地址
producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
//启动producer实例
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
for (int i = 0; i < 2; i++) {
final int index = i;
//创建消息并指定Topic,Tag和消息体
Message msg = new Message("TopicTest",
"TagA",
"OrderID188",
"Hello Word".getBytes(RemotingHelper.DEFAULT_CHARSET));
//sendCallback接收异步返回结果回调
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d ok %s %n", index, sendResult.getMsgId());
}
@Override
public void onException(Throwable throwable) {
System.out.printf("%-10d Exception %s %n", index, throwable, throwable.getStackTrace());
}
});
}
//不发送消息关闭producer 实例
// producer.shutdown();
}
3)单向发送消息
这种方式主要用在不特别关心发送结果的场景,例如日志发送
public class OnewayProducer {
public static void main(String[] args) throws Exception{
//实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
//这只NameServer 的地址
producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
//启动Producer实例
producer.start();
for (int i=0; i<4; i++){
//创建消息,并指定,Topic,Tag和消息体
Message msg = new Message("TopicTest",
"TagA",
("Hello Oneway RocketMQ - " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
//发送单向消息,没有任何返回结果
producer.sendOneway(msg);
}
producer.shutdown();
}
}
消费消息
1)负载均衡模式
消费者采用负载均衡方式消费消息,多个消息者共同消费队列消息,每个消费者处理的消息不同
public class Consumption {
public static void main(String[] args) throws Exception{
//实例化消息生产者,指定组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
//指定nameserver地址
consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
//订阅topic
consumer.subscribe("TopicTest", "*");
//负载均衡消息
consumer.setMessageModel(MessageModel.CLUSTERING);
//注册回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
Iterator<MessageExt> it = msgs.iterator();
while(it.hasNext()){
MessageExt me = it.next();
if(me.getTopic().equals("TopicTest")){
byte[] bytes = me.getBody();
System.out.printf("消息内容是:%s %n", new String(bytes));
}
}
System.out.printf("%s Receive New Message: %s %n", Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
System.out.printf("Consumer Started.%n");
}
}
2)广播模式
消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的
public class Broadcast {
public static void main(String[] args) throws Exception{
// 实例化消息生产者,指定组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 指定指定NameServer地址信息
consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
// 订阅Topic
consumer.subscribe("TopicTest", "*");
// 负载均衡模式消费
consumer.setMessageModel(MessageModel.BROADCASTING);
// 注册回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s BroadCast Receive New Message: %s %n",
Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消息
consumer.start();
System.out.printf("Consumer Started. %n");
}
}
二、顺序消息
顺序消息生产
消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。
顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。
下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。
public class Producer {
public static void main(String[] args) throws Exception{
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
producer.start();
String[] tags = new String[]{"TagA", "TagC", "TagD"};
//订单列表
List<OrderStep> orderStepList = new Producer().buildOrders();
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M|M-dd HH:mm:ss");
String dateStr = sdf.format(date);
for(int i=0; i<10; i++){
//加时间戳
String body = dateStr + " Hello RocketMQ " + orderStepList.get(i);
Message message = new Message("TopicTest", tags[i % tags.length], "KEY" + i, body.getBytes());
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message message, Object o) {
long id = (Long)o; //根据订单id选择发送queue
long index = id % mqs.size();
return mqs.get((int)index);
}
}, orderStepList.get(i).getOrderId()); //订单id
System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s",
sendResult.getSendStatus(),
sendResult.getMessageQueue().getQueueId(),
body
));
}
// producer.shutdown();
}
/**
* 生成模拟订单数据
*/
private List<OrderStep> buildOrders() {
List<OrderStep> orderList = new ArrayList<OrderStep>();
OrderStep orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111065L);
orderDemo.setDesc("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103117235L);
orderDemo.setDesc("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111065L);
orderDemo.setDesc("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103117235L);
orderDemo.setDesc("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111065L);
orderDemo.setDesc("完成");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("推送");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103117235L);
orderDemo.setDesc("完成");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(15103111039L);
orderDemo.setDesc("完成");
orderList.add(orderDemo);
return orderList;
}
}
public class OrderStep {
private long orderId;
private String desc;
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "OrderStep{" +
"orderId=" + orderId +
", desc='" + desc + '\'' +
'}';
}
}
顺序消费消息
public class ConsumerInOrder {
public static void main(String[] args) throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");
consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
/**
* 设置Consumer 第一次启动时从队列头部开始消费还是从队列尾部开始消费<br>
* 如果非第一次启动那么按照上次消费的顺序继续消费
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("TopicTest", "TagA || TagC || TagD");
consumer.registerMessageListener(new MessageListenerOrderly() {
Random random = new Random();
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
context.setAutoCommit(true);
for(MessageExt msg:msgs){
//可以看到每一个queue都有唯一的线程来消费,订单对应每一个queue(却分)有序
System.out.println("consumerThread=" + Thread.currentThread().getName() + "queue=" + msg.getQueueId() + ",content:" + new String(msg.getBody()));
}
try {
//模拟业务逻辑处理中。。。。
TimeUnit.SECONDS.sleep(random.nextInt(10));
}catch(Exception e){
e.printStackTrace();
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started.");
}
}
测试结果如下图所示


三、延时消息
延时消息消费者
public class ScheduledMessageConsumer{
public static void main(String[] args) throws Exception {
// 实例化消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer");
consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
//订阅Topics
consumer.subscribe("TestTopic", "*");
//注册消费则监听
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
for(MessageExt message : messages){
//print approximate delay time period
System.out.println("Receive message [msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later");
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
System.out.println("Consumer Started.");
}
}
延时消息生产者
public class ScheduledMessageProducer {
public static void main(String[] args) throws Exception{
//实例化一个生产者来生产延迟消息
DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
//启动生产者
producer.start();
int totalMessageToSend = 3;
for(int i=0; i < totalMessageToSend; i++){
Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());
//设置延迟等级3,这个消息将在10秒之后发送(现在只支持固定的几个时间, 详情请看delayTimeLevel)
message.setDelayTimeLevel(3);
//发送消息
producer.send(message);
}
producer.shutdown();
}
}
使用限制
// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18
测试结果如下图所示:

四、批量消息
发送批量消息
如果每次只发送不超过4MB的消息,则很容易使用处理,样例如下:
String topic = "BatchTest";
List<Message> messages = new ArrayList<>();
messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
try {
producer.send(messages);
} catch (Exception e) {
e.printStackTrace();
//处理error
}
如果消息的总长度可能大于4MB时,这时候最好把消息进行分割
public class ListSplitter implements Iterator<List<Message>> {
private final int SIZE_LIMIT = 1024 * 1024 * 4;
private final List<Message> messages;
private int currIndex;
public ListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override
public boolean hasNext() {
return currIndex < messages.size();
}
@Override
public List<Message> next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
Map<String, String> properties = message.getProperties();
for (Map.Entry<String, String> entry : properties.entrySet()) {
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20; // 增加日志的开销20字节
if (tmpSize > SIZE_LIMIT) {
//单个消息超过了最大的限制
//忽略,否则会阻塞分裂的进程
if (nextIndex - currIndex == 0) {
//假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环
nextIndex++;
}
break;
}
if (tmpSize + totalSize > SIZE_LIMIT) {
break;
} else {
totalSize += tmpSize;
}
}
List<Message> subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
}
//把大的消息分裂成若干个小的消息
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
try {
List<Message> listItem = splitter.next();
producer.send(listItem);
} catch (Exception e) {
e.printStackTrace();
//处理error
}
}
五、过滤消息
在大多数情况下,TAG是一个简单而且有用的设计,其可以来选择你想要的消息。例如:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");
消息者将接收包含TAGA或TAGB或TAGC的消息,但是限制是一个消息只能有一个标签,者对于复杂场景可能不起作用。在这种情况下哎,可以使用SQL表达是筛选消息。SQL特性可以通过发送消息时的属性进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:
------------
| message |
|----------| a > 5 AND b = 'abc'
| a = 10 | --------------------> Gotten
| b = 'abc'|
| c = true |
------------
------------
| message |
|----------| a > 5 AND b = 'abc'
| a = 1 | --------------------> Missed
| b = 'abc'|
| c = true |
------------
SQL基本语法
RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。
- 数值比较,比如:
>,>=,<,<=,BETWEEN,=; - 字符比较,比如:
=,<>,IN; IS NULL或者IS NOT NULL;- 逻辑符号
AND,OR,NOT;
常量支持类型为:
- 数值,比如:
123,2.1315; - 字符,比如:
'abc',必须用单引号包裹起来; null,特殊的常量- 布尔值,
TRUE或FALSE
只有使用push模式的消费者才能使用SQL92标准语句,接口入校:
public void subscribe(finalString topic, final MessageSelector messageSelector)
消息生产者
返送消息时,你能通过
putUserProperty来设置消息的属性
public class Producer {
public static void main(String[] args) throws Exception{
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.setNamesrvAddr("192.168.0.123:9876;192.168.0.22:9876");
producer.start();
String[] tags = {"TagA", "TagB", "TagC", "TagD"};
for(int i=0; i< 20; i++){
//创建消息并指定topic,tag消息体
String tag = tags[i % tags.length];
//构建消息
Message msg = new Message("TopicTest",
tag,
("hello RocketMQ age- " + i + ",消息TAG - " + tag ).getBytes(RemotingHelper.DEFAULT_CHARSET));
msg.putUserProperty("age", String.valueOf(i));
//5.发送消息
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达TopicTest
System.out.printf("%s%n", "发送消息状态" + sendResult);
}
producer.shutdown();
}
}
消息消费者
用MessageSelector.bySql来使用sql筛选消息
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
//指定nameserver地址
consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
//只有订阅的消息有这个属性a, a>=0 and a<3
consumer.subscribe("TopicTest", MessageSelector.bySql("age between 5 and 13"));
//负载均衡消息
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
try {
for(MessageExt msg:msgs){
String msgbody = new String(msg.getBody(), "utf-8");
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
Date date = new Date(msg.getStoreTimestamp());
System.out.println("comsumer=== 存入时间:" + sdf.format(date) + " === MessageBody:" +msgbody );
}
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer===启动成功!");
}
}
测试结果如下图所示:

事务消息
流程分析

上面说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送以及提交、事务消息的补偿流程
1)是无消息发送及提交
(1)发送消息(half消息)
(2)服务端响应消息写入结果
(3)根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
(4)根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
2)事务补偿
(1)对没有Commit/Rollback的是无消息(pending状态的消息),从服务端发起一次"回查"
(2)Producer收到回查消息,检查会差消息对应的本地事务的状态
(3)根据本地事务状态,重新Commit或者Rollback发生超时或者失败的情况
3)事务消息状态
事务消息共有三种状态,提交状态、回滚状态、中间状态:
TransactionStatus.CommitTransaction:提交事务,它允许消费者消费消息。TransactionStatus.RollbackTransaction:回滚事务,它代表消息被删除,不允许被消费。TransactionStatus.Unknown:中间状态,它代表需要检查消息队列来确定状态。
4)创建事务性生产者
使用
TransactionMQProducer类创建生产者,并指定唯一的ProducerGroup,就可以设置自定义线程池来处理这些检查请求。执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态再请参考前一节。
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
//创建事务监听器
TransactionListener transactionListener = new TransactionListenerImpl();
//创建消息生产者
TransactionMQProducer producer = new TransactionMQProducer("group6");
producer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
//生产者这是监听器
producer.setTransactionListener(transactionListener);
//启动消息生产者
producer.start();
String[] tags = new String[]{"TagA", "TagB", "TagC"};
for (int i = 0; i < 3; i++) {
try {
Message msg = new Message("TransactionTopic", tags[i % tags.length], "KEY" + i,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
System.out.printf("%s%n", sendResult);
TimeUnit.SECONDS.sleep(1);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//producer.shutdown();
}
}
5)实现事务的监听接口
当发送半消息成功时,我们使用
executeLocalTransaction方法来执行本地事务。它返回前一节中提到的三个事务状态之一。checkLocalTrancation方法用于检查本地事务状态,并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。
public class TransactionListenerImpl implements TransactionListener {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
System.out.println("执行本地事务");
if (StringUtils.equals("TagA", msg.getTags())) {
return LocalTransactionState.COMMIT_MESSAGE;
} else if (StringUtils.equals("TagB", msg.getTags())) {
return LocalTransactionState.ROLLBACK_MESSAGE;
} else {
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("MQ检查消息Tag【"+msg.getTags()+"】的本地事务执行结果");
return LocalTransactionState.COMMIT_MESSAGE;
}
}
使用限制
- 事务消息不支持延迟消息和批量消息
- 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为15次,但是用户可以通过Broker配置文件
transactionCheckMax茶树来修改此限制。如果已经检查某条消息超过N次的话(N=TransactionCheckMax)则Broker将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写AbtractTransactionCheckListener类。来修改这个行为。- 事务消息将在Broker配置文件中的参数
tansactionMsgTimeout这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性CHECK_IMMUNITY_IN_SECOND来改变这个限制,改参数优先于transactionMsgTimeout属性- 事务性消息可能不止一次被检查或消费。
- 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定,它的高可用性通过RocketMQ本身的高可用性机制来保证,如果希望确保消息不丢失、并且事务完整性得到保证,建议使用同步的双重写机制
- 事务消息的生产者ID不能与其它类型消息的生产者ID共享。与其它类型的消息不同,事务消息允许反向查询、MQ服务器通过他们的生产者ID查询消费者。

浙公网安备 33010602011771号