普通消息一般应用于微服务解耦、事件驱动、数据集成等场景,这些场景大多数要求数据传输通道具有可靠传输的能力,且对消息的处理时机、处理顺序没有特别要求。如:上游订单系统支付后,下游的物流配送系统、用户积分系统的相关操作。
主题的类型与消息的类型要对应,所以普通消息的主题类型为 Normal
./mqadmin updatetopic -n localhost:9876 -c DefaultCluster -t MY_NORMAL_TOPIC -a +message.type=NORMAL
示例中创建了一个名为 MY_NORMAL_TOPIC 的普通消息主题
注:我们的入门示例其实就是一个普通消息,只是我们没有设置消息类型,不设置默认就是NORMAL
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.message.Message;
import org.apache.rocketmq.client.apis.producer.Producer;
import org.apache.rocketmq.client.apis.producer.SendReceipt;
import java.io.IOException;
public class NormalProducerDemo {
public static void main(String[] args) throws ClientException {
// 用于提供:生产者、消费者、消息对应的构建类 Builder
ClientServiceProvider provider = ClientServiceProvider.loadService();
// 构建配置类(包含端点位置、认证以及连接超时等的配置)
ClientConfiguration configuration = ClientConfiguration.newBuilder()
// endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
.setEndpoints(MyMQProperties.ENDPOINTS)
.build();
// 构建生产者
Producer producer = provider.newProducerBuilder()
// Topics 列表:生产者和主题是多对多的关系,同一个生产者可以向多个主题发送消息
.setTopics("MY_NORMAL_TOPIC","TestTopic")
.setClientConfiguration(configuration)
// 构建生产者,此方法会抛出 ClientException 异常
.build();
// 构建消息类
Message message = provider.newMessageBuilder()
// 设置消息发送到的主题
.setTopic("MY_NORMAL_TOPIC")
// 设置消息索引键,可根据关键字精确查找某条消息。其一般为业务上的唯一值。如:订单id
.setKeys("order_id_1001")
// 设置消息Tag,用于消费端根据指定Tag过滤消息。其一般用作区分不同的业务,最好给它定义好命名规范
.setTag("ORDER_SUBMIT")
// 消息体,单条消息的传输负载不宜过大。所以此处的字节大小最好有个限制
.setBody("{\"success\":true}".getBytes())
.build();
// 发送消息(此处最好进行异常处理,对消息的状态进行一个记录)
try {
SendReceipt sendReceipt = producer.send(message);
System.out.println("Send message successfully, messageId=" + sendReceipt.getMessageId());
} catch (ClientException e) {
System.out.println("Failed to send message");
}
// 发送完,关闭生产者
try {
producer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
final CompletableFuture<SendReceipt> future = producer.sendAsync(message);
ExecutorService sendCallbackExecutor = Executors.newCachedThreadPool();
future.whenCompleteAsync((sendReceipt, throwable) -> {
if (null != throwable) {
System.out.println("Failed to send message" + throwable.getMessage());
// Return early.
return;
}
System.out.println("Send message successfully, messageId={}" + sendReceipt.getMessageId());
}, sendCallbackExecutor);
RocketMQ 一般传输的是都是业务事件数据。单个原子消息事件的数据大小需要严格控制,如果单条消息过大容易造成网络传输层压力,不利于异常重试和流量控制。
系统默认的消息最大限制如下:
生产环境中如果需要传输超大负载,建议按照固定大小做报文拆分,或者结合文件存储等方法进行传输。
RocketMQ 的生产者和主题是多对多的关系,支持同一个生产者向多个主题发送消息。对于生产者的创建和初始化,建议遵循够用即可、最大化复用原则,如果有需要发送消息到多个主题的场景,无需为每个主题都创建一个生产者。
RocketMQ 的生产者是可以重复利用的底层资源,类似数据库的连接池。因此不需要在每次发送消息时动态创建生产者,且在发送结束后销毁生产者。这样频繁的创建销毁会在服务端产生大量短连接请求,严重影响系统性能。
综上,我们的示例仅为示例,请勿轻易在生产环境中直接使用。实际中可能需要将其定义为一个线程安全的单例,更复杂一点可能需要定义为对象池的方式来提供生产者实例。
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.PushConsumer;
import java.nio.ByteBuffer;
import java.util.Collections;
public class NormalConsumerDemo {
public static void main(String[] args) throws ClientException {
// 用于提供:生产者、消费者、消息对应的构建类 Builder
ClientServiceProvider provider = ClientServiceProvider.loadService();
// 构建配置类(包含端点位置、认证以及连接超时等的配置)
ClientConfiguration configuration = ClientConfiguration.newBuilder()
// endpoints 即为 proxy 的地址,多个用分号隔开。如:xxx:8081;xxx:8081
.setEndpoints(MyMQProperties.ENDPOINTS)
.build();
// 设置过滤条件(这里为使用 tag 进行过滤)
String tag = "ORDER_SUBMIT";
FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);
// 构建消费者
PushConsumer pushConsumer = provider.newPushConsumerBuilder()
.setClientConfiguration(configuration)
// 设置消费者分组
.setConsumerGroup("MY_ORDER_SUBMIT_GROUP")
// 设置主题与消费者之间的订阅关系
.setSubscriptionExpressions(Collections.singletonMap("MY_NORMAL_TOPIC", filterExpression))
.setMessageListener(messageView -> {
System.out.println(messageView);
ByteBuffer rs = messageView.getBody();
byte[] rsByte = new byte[rs.limit()];
rs.get(rsByte);
System.out.println("Message body:" + new String(rsByte));
// 处理消息并返回消费结果。
System.out.println("Consume message successfully, messageId=" + messageView.getMessageId());
return ConsumeResult.SUCCESS;
}).build();
// 如果不需要再使用 PushConsumer,可关闭该实例。
// pushConsumer.close();
}
}
messageView 对象还可以获取 messageId 、Topic 等信息。
这里有消费者分组、消息过滤等知识点,我们后续都会提到,这里先有个体验即可。
RocketMQ 的消费者在通信协议层面支持非阻塞传输模式,网络通信效率较高,并且支持多线程并发访问。因此,大部分场景下,单一进程内同一个消费分组只需要初始化唯一的一个消费者即可,开发过程中应避免以相同的配置初始化多个消费者。
RocketMQ 的消费者是可以重复利用的底层资源,类似数据库的连接池。因此不需要在每次接收消息时动态创建消费者,且在消费完成后销毁消费者。这样频繁地创建销毁会在服务端产生大量短连接请求,严重影响系统性能。
import com.yyoo.mq.rocket.MyMQProperties;
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;
public class NormalProducerDemo {
/**
* 生产者分组
*/
private static final String PRODUCER_GROUP = "NORMAL_PRODUCER_GROUP";
/**
* 主题
*/
private static final String TOPIC = "MY_NORMAL_TOPIC";
public static void main(String[] args) throws MQClientException {
/*
* 创建生产者,并使用生产者分组初始化
*/
DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
/*
* NamesrvAddr 的地址,多个用分号隔开。如:xxx:9876;xxx:9876
*/
producer.setNamesrvAddr(MyMQProperties.NAMESRV_ADDR);
/*
* 发送消息超时时间,默认即为 3000
*/
producer.setSendMsgTimeout(3000);
/*
* 启动生产者,此方法抛出 MQClientException
*/
producer.start();
/*
* 发送消息
*/
for (int i = 1; i <= 2; i++) {
try {
Message msg = new Message();
msg.setTopic(TOPIC);
// 设置消息索引键,可根据关键字精确查找某条消息。
msg.setKeys("messageKey");
// 设置消息Tag,用于消费端根据指定Tag过滤消息。
msg.setTags("messageTag");
// 设置消息体
msg.setBody(("messageBody" + i).getBytes());
// 此为同步发送方式
/*SendResult rs = producer.send(msg);
System.out.printf("%s%n",rs);*/
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("消息发送成功:%s%n", sendResult);
}
@Override
public void onException(Throwable e) {
System.out.println("消息发送失败:" + e.getMessage());
}
});
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送失败!i = " + i);
}
}
// 如果生产者不再使用,则调用关闭
// 异步发送消息注意:异步发送消息,建议此处不关闭或者在sleep一段时间后再关闭
// 因为异步 SendCallback 执行的时候,shutdow可能已经执行了,生产者被关闭了
// producer.shutdown();
}
}
import com.yyoo.mq.rocket.MyMQProperties;
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.consumer.ConsumeFromWhere;
public class NormalConsumerDemo {
/**
* 设置消费者分组
*/
public static final String CONSUMER_GROUP = "NORMAL_CONSUMER_GROUP";
/**
* 主题
*/
public static final String TOPIC = "MY_NORMAL_TOPIC";
public static void main(String[] args) throws MQClientException {
/*
* 通过消费者分组,创建消费者
*/
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);
/*
* NamesrvAddr 的地址,多个用分号隔开。如:xxx:9876;xxx:9876
*/
consumer.setNamesrvAddr(MyMQProperties.NAMESRV_ADDR);
/*
* 指定从哪一个消费位点开始消费 CONSUME_FROM_FIRST_OFFSET 表示从第一个开始
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
/*
* 消费者订阅的主题,和过滤条件
* 我们这里使用 * 表示,消费者消费主题下的所有消息,多个tag 使用 || 隔开
*/
consumer.subscribe(TOPIC, "*");
/*
* 注册消费监听
*/
consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
/*
* 启动消费者.
*/
consumer.start();
System.out.printf("Consumer Started.%n");
// 如果消费者不再使用,关闭
// consumer.shutdown();
}
}