建议先看【消息队列】MQ进阶篇之 RocketMQ 的理论,非常通俗易懂。不懂你来找我!
一、客户端配置
添加客户端依赖在 pom.xml 中
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.4</version>
</dependency>
二、生产者发送
Apache RocketMQ可用于以三种方式发送消息:同步、异步和单向传输。前两种消息类型是可靠的,因为无论它们是否成功发送都有响应。
下面是三种方式的解析图解。
1、举例详细介绍一下同步消息发送:
- 首先会创建一个producer。普通消息可以创建 DefaultMQProducer,创建时需要填写生产组的名称,生产者组是指同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。
- 设置 NameServer 的地址。Apache RocketMQ很多方式设置NameServer地址(客户端配置中有介绍),这里是在代码中调用producer的API setNamesrvAddr进行设置,如果有多个NameServer,中间以分号隔开,比如"127.0.0.2:9876;127.0.0.3:9876"。
- 第三步是构建消息。指定topic、tag、body等信息,tag可以理解成标签,对消息进行再归类,RocketMQ可以在消费端对tag进行过滤。
- 最后调用send接口将消息发送出去。同步发送等待结果最后返回SendResult,SendResut包含实际发送状态还包括SEND_OK(发送成功),FLUSH_DISK_TIMEOUT(刷盘超时), FLUSH_SLAVE_TIMEOUT(同步到备超时),SLAVE_NOT_AVAILABLE(备不可用),如果发送失败会抛出异常。
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 初始化一个producer并设置Producer group name
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); //(1)
// 设置NameServer地址
producer.setNamesrvAddr("localhost:9876"); //(2)
// 启动producer
producer.start();
for (int i = 0; i < 100; i++) {
// 创建一条消息,并指定topic、tag、body等信息,tag可以理解成标签,对消息进行再归类,RocketMQ可以在消费端对tag进行过滤
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
); //(3)
// 利用producer进行发送,并同步等待发送结果
SendResult sendResult = producer.send(msg); //(4)
System.out.printf("%s%n", sendResult);
}
// 一旦producer不再使用,关闭producer
producer.shutdown();
}
}
在使用 RocketMQ 发送消息的项目中,通常的做法是在项目启动时初始化 RocketMQ 的生产者(Producer),并在需要发送消息时使用该生产者发送消息。这样可以保证在整个项目运行期间都有可用的生产者实例,并避免在每次发送消息时都初始化生产者,提高效率。
具体的步骤包括:
-
初始化生产者: 在项目启动时,初始化 RocketMQ 生产者。这通常涉及设置生产者的配置,如 NameServer 地址、生产者组名等。初始化生产者的时候,可以选填一些其他参数,举例说明,第二个参数类型Boolean ,表示是否开启消息轨迹。
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup",true); producer.setNamesrvAddr("localhost:9876"); producer.start();
-
发送消息: 在需要发送消息的地方,使用初始化好的生产者发送消息。
Message message = new Message("TopicTest", "TagA", "HelloRocketMQ".getBytes(StandardCharsets.UTF_8)); SendResult sendResult = producer.send(message);
-
关闭生产者: 在项目关闭或其他适当的时机,关闭 RocketMQ 生产者。
java Copy code producer.shutdown();
通过这样的方式,生产者在项目启动时初始化,一直处于可用状态,而不需要每次发送消息都重新初始化。这有助于提高性能和效率。spring项目中自启动参考【注解】@Bean(initMethod = “start“, destroyMethod= “shutdown“)
请注意,生产者的初始化是一个相对耗时的操作,因此最好在项目启动时进行。另外,RocketMQ 的生产者是线程安全的,可以在整个应用程序的生命周期内重复使用。
2、异步消息发送
异步发送是指发送方发出一条消息后,不等服务端返回响应,接着发送下一条消息的通讯方式。
消息发送方在发送了一条消息后,不需要等待服务端响应即可发送第二条消息,发送方通过回调接口接收服务端响应,并处理响应结果。异步发送一般用于链路耗时较长,对响应时间较为敏感的业务场景。例如,视频上传后通知启动转码服务,转码完成后通知推送转码结果等。
异步发送与同步发送代码唯一区别在于调用send接口的参数不同,异步发送不会等待发送返回,取而代之的是send方法需要传入 SendCallback
的实现,SendCallback
接口主要有onSuccess
和 onException
两个方法,表示消息发送成功和消息发送失败。此处和前端的promise有异曲同工之处,对前端有兴趣的同学可以参考谈谈前端开发中的同步和异步,promise、async/await,应用场景,以及在事件循环机制中的区别
示例代码,来自官方文档。
public class AsyncProducer {
public static void main(String[] args) throws Exception {
// 初始化一个producer并设置Producer group name
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
// 设置NameServer地址
producer.setNamesrvAddr("localhost:9876");
// 启动producer
producer.start();
producer.setRetryTimesWhenSendAsyncFailed(0);
int messageCount = 100;
final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
for (int i = 0; i < messageCount; i++) {
try {
final int index = i;
// 创建一条消息,并指定topic、tag、body等信息,tag可以理解成标签,对消息进行再归类,RocketMQ可以在消费端对tag进行过滤
Message msg = new Message("TopicTest",
"TagA",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
// 异步发送消息, 发送结果通过callback返回给客户端
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d OK %s %n", index,
sendResult.getMsgId());
countDownLatch.countDown();
}
@Override
public void onException(Throwable e) {
System.out.printf("%-10d Exception %s %n", index, e);
e.printStackTrace();
countDownLatch.countDown();
}
});
} catch (Exception e) {
e.printStackTrace();
countDownLatch.countDown();
}
}
//异步发送,如果要求可靠传输,必须要等回调接口返回明确结果后才能结束逻辑,否则立即关闭Producer可能导致部分消息尚未传输成功
countDownLatch.await(5, TimeUnit.SECONDS);
// 一旦producer不再使用,关闭producer
producer.shutdown();
}
}
3、单向发送
发送方只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
单向模式调用方法 sendOneway
,不会对返回结果有任何等待和处理。其他和同步异步发送消息逻辑一致。
三、消费者实践
有pull 和 push 两种消费者,介绍参考【消息队列】MQ进阶篇之 RocketMQ 的理论。此处以 push 消费者在项目中的实践举例说明。
1、创建消费者实例:
使用 DefaultMQPushConsumer 类创建一个消费者实例,并设置消费者组、NameServer 地址等参数。
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("YourConsumerGroup");
consumer.setNamesrvAddr("localhost:9876");
2、注册消息监听器:
注册消息监听器,实现消息的消费逻辑。在监听器中处理业务逻辑,并返回消费状态。
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.common.message.MessageExt;
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
// 处理消息的业务逻辑...
System.out.println("Received message: " + new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
3、订阅主题和标签:
使用 subscribe 方法订阅消息主题和标签。
consumer.subscribe("YourTopic", "YourTag");
4、启动消费者:
启动消费者实例,开始接收和消费消息。此处也同生产者在spring中的应用,可参考 spring项目中自启动参考【注解】@Bean(initMethod = “start“, destroyMethod= “shutdown“)
consumer.start();
5、关闭消费者:
在程序结束时,需要关闭消费者实例。
consumer.shutdown();
注意事项:
- 消费者组(Consumer Group): 每个消费者都应该属于一个消费者组,同一个消费者组内的消费者将共同消费相同的消息,实现负载均衡和容错。
- 并发消费: 在实际应用中,可以考虑使用多线程来实现并发消费,提高消息处理的效率。需要注意线程安全和业务逻辑的一致性。
- 消息重试: 如果消息处理失败,RocketMQ 会进行消息重试。因此,消费者的业务逻辑应该是幂等的,即多次执行不会产生不同的结果。
- 异常处理: 在消息监听器中处理异常,可以记录日志、进行重试等操作,确保消息不会因为消费失败而丢失。
- 定时消息: 如果涉及到定时消息,注意处理定时消息的特殊逻辑。
- 消息过滤: 在订阅消息时,可以通过设置标签(Tag)来进行消息过滤,只消费感兴趣的消息。
以上是一个简单的 RocketMQ 消费者的实践流程。根据实际业务需求,可能需要考虑更多的细节和调优。在设计消费者时,根据业务场景和需求选择合适的消费模式、并发处理方式和异常处理策略。
持续更新ing , 动动小手,点点关注,后续更精彩!