RocketMQ 原生API使用
普通消息
生产者
单向发送
// 创建生产者,指定生产者组(oneway-producer-group)
DefaultMQProducer producer = new DefaultMQProducer("oneway-producer-group");
// 指定 NameServer
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动生产者
producer.start();
// 构建消息,指定 Topic(onewayTopic)
Message message = new Message("onewayTopic", "我是一个单向消息".getBytes());
// 单向发送,没有返回值,不知道成功与否
producer.sendOneway(message);
// 关闭生产者
producer.shutdown();
同步发送
DefaultMQProducer producer = new DefaultMQProducer("test-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
// 发送 10 个消息,这10个消息将分摊到4个队列(2-2-3-3)
for (int i = 0; i < 10; i++) {
Message message = new Message("testTopic", "我是一个简单的消息".getBytes());
// 同步发送,有返回值(返回值中有消息ID,发送到哪个队列了,位点等信息)
SendResult sendResult = producer.send(message);
System.out.println("消息发送状态:" + sendResult.getSendStatus());
}
producer.shutdown();
异步发送
DefaultMQProducer producer = new DefaultMQProducer("async-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message("asyncTopic", "我是一个异步消息".getBytes());
// 回调方法接收服务器响应,是异步的,不阻塞当前
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功");
}
@Override
public void onException(Throwable e) {
System.err.println("发送失败:" + e.getMessage());
}
});
producer.shutdown();
消费者
// 创建一个消费者(消费者组为 test-consumer-group)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-consumer-group");
// 连接 name server
consumer.setNamesrvAddr("127.0.0.1:9876");
// 订阅一个主题 * 标识订阅这个主题中所有的消息(可以根据 Tag 过滤)
consumer.subscribe("testTopic", "*");
// 设置一个监听器(MessageListenerConcurrently:并发模式)
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
// 全部内容有很多,包括消息头和消息体,消息体就是生产者发送的内容
System.out.println("消息全部内容" + msgs.get(0).toString());
System.out.println("消息体:" + new String(msgs.get(0).getBody()));
System.out.println("消费上下文:" + context);
// 返回值 CONSUME_SUCCESS 成功,消息会从 mq 出队
// 返回值 RECONSUME_LATER 失败, 消息会重新回到队列(默认重试16次)
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动
consumer.start();
批量消息
- 生产者把一批消息一起发送,只发送一次
- 消费者还是一条一条的来消费
生产者
DefaultMQProducer producer = new DefaultMQProducer("batch-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
// 构建消息
List<Message> msgs = Arrays.asList(
new Message("batchTopic", "我是一组消息的A消息".getBytes()),
new Message("batchTopic", "我是一组消息的B消息".getBytes()),
new Message("batchTopic", "我是一组消息的C消息".getBytes())
);
// send 被重载过,支持传入一个 List<Message>,虽然多个消息,但是是一次发送,所以这批消息将进入同一个队列中
SendResult send = producer.send(msgs);
System.out.println(send);
producer.shutdown();
消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("batch-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("batchTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
// 会打印 3 次(一次发送多个消息,消费时还是一条一条的来消费)
System.out.println("收到消息了" + new Date());
System.out.println(msgs.size());
System.out.println(new String(msgs.get(0).getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
延迟消息
- 4.x 只支持级别,不同级别对应不同延迟时间,这个可以改,要到配置文件 conf 里改;
- 5.x 可以指定时间了
- 消费者没有需要注意的地方,和前面的示例一样即可
DefaultMQProducer producer = new DefaultMQProducer("ms-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message("orderMsTopic", "我是一个延迟消息".getBytes());
// 给消息设置一个延迟时间,不同的延迟级别表示不同的延迟时间
// messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"
message.setDelayTimeLevel(3);
producer.send(message);
System.out.println("发送时间" + new Date());
producer.shutdown();
事务消息
顺序消息
- 一个消费者对应一个队列,所以消息不能投递到不同的队列,所以在发消息时,同一组顺序消息要发送到相同的队列中(定义算法来选择队列实现)
- 消费者默认是并发模式(多线程),因为多线程天生保证不了顺序,所以不能使用并发模式,要使用顺序模式(单线程+锁实现)
生产者
DefaultMQProducer producer = new DefaultMQProducer("ordered_producer_group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
// 3 个订单
String[] orderIds = {"ORDER_001", "ORDER_002", "ORDER_003"};
// 每个订单 4 个步骤,顺序执行(创建、支付、发货、完成)
String[] orderSteps = {"CREATE", "PAY", "SHIP", "COMPLETE", "CANCEL"};
for (String orderId : orderIds) { // 订单
for (String step : orderSteps) { // 步骤
// 创建消息,指定Topic、Tag和消息体
Message msg = new Message("OrderTopic", "Order", (orderId + ":" + step).getBytes());
// 发送顺序消息(是 MessageQueueSelector,而不是 MessageListenerConcurrently )
producer.send(msg, new MessageQueueSelector() {
// 这个方法就是在选择队列(arg 是外部 send() 的第三个参数 orderId)
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
String orderId = (String) arg;
int index = Math.abs(orderId.hashCode()) % mqs.size();
return mqs.get(index);
}
}, orderId); // 使用orderId作为选择队列的参数,会传到 select() 方法里
System.out.printf("Send ordered message: %s%n", new String(msg.getBody()));
// 模拟处理间隔
Thread.sleep(500);
}
}
// 关闭生产者
producer.shutdown();
消费者
RocketMQ 内部是给队列上锁,消费者处理消息前要先获取锁,处理完后要释放锁,持有锁期间别的线程
已经是单线程了,为什么还需要锁?消费者是单线程,但是可能多个消费者消费同一个队列,还是会出现并发
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ordered_consumer_group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("OrderTopic", "Order");
// MessageListenerConcurrently 并发模式(多线程消费,消费失败默认重试16次)
// MessageListenerOrderly 顺序模式(单线程的,消费失败默认重试次数为 Integer.Max_Value)
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
String message = new String(msg.getBody());
System.out.printf(new String(msg.get(0).getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("Ordered consumer started.");
消息 Tag 过滤
发消息时:topic 用来确定队列,tag 不参与队列的选择,tag 会作为消息属性与消息一起存储在队列上
消费消息时:borker 会把队列中的消息根据 tag 确定出要推送的消费者
生产者
发送两条消息,一条消息 tag 为 vip1,一条消息 tag 为 vip2
DefaultMQProducer producer = new DefaultMQProducer("tag-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message("tagTopic", "vip1", "vip1消息".getBytes());
Message message2 = new Message("tagTopic", "vip2", "vip2消息".getBytes());
producer.send(message);
producer.send(message2);
producer.shutdown();
消费者1
borker 会把 tag 是 vip1 的消息推送给当前消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group-a");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("tagTopic", "vip1");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.println("消费者1:" + new String(msgs.get(0).getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
消费者2
borker 会把两条消息(tag 是 vip1、vip2)都推送给当前消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group-b");
consumer.setNamesrvAddr("127.0.0.1:9876");
// 订阅 tagTopic,接收 tag 为 vip1 或 vip2 的消息(两个消息都会接收)
consumer.subscribe("tagTopic", "vip1 || vip2");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.println("消费者2:" + new String(msgs.get(0).getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
消息指定 Key
生产者
DefaultMQProducer producer = new DefaultMQProducer("key-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
String key = UUID.randomUUID().toString();
// topic、tag、key、msg
Message message = new Message("keyTopic", "vip1", key, "我是带key的消息".getBytes());
producer.send(message);
producer.shutdown();
消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("key-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("keyTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
MessageExt messageExt = msgs.get(0);
System.out.println("消息主体:" + new String(messageExt.getBody()));
System.out.println("消息key:" + messageExt.getKeys());
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
重试机制
生产者
DefaultMQProducer producer = new DefaultMQProducer("retry-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
// 发送失败重试次数
producer.setRetryTimesWhenSendFailed(2);
producer.setRetryTimesWhenSendAsyncFailed(2);
String key = UUID.randomUUID().toString();
Message message = new Message("retryTopic", "vip1", "我是一条重试消息".getBytes());
producer.send(message);
producer.shutdown();
消费者
并发模式 MessageListenerConcurrently
- 最重试次数:16(默认)
- 重试间隔:10s, 30s, 1m, 2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 10m, 20m, 30m, 1h, 2h
- 消费失败,未超过最大重试次数时,消息进入重试队列,队列的 Topic 为
%RETRY%消费者组名称
这时可以通过getReconsumeTimes()获取到重试次数 - 消费失败,超过最大重试次数时,消息进入死信队列,队列的 Topic 为
%DLQ%消费者组名称 - 进入死信队列的消息处理:创建订阅这个死信队列 Topic 的消费者
业务中可以在重试达到多少次的时候记录下来,人工进行干预,避免进入死信队列
/**
* 如果进入死信队列,这个队列的 Topic 为:%DLQ%retry-consumer-group
*/
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("retryTopic", "*");
// 并发模式:设置最大重试次数
consumer.setMaxReconsumeTimes(10);
// 并发模式:设置重试队列的延迟级别
consumer.setDelayLevelWhenNextConsume(3);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
MessageExt messageExt = msgs.get(0);
// 重试次数(并发模式下才会重试,并发模式下才能通过此方法获取当前消息的重试次数)
int times = messageExt.getReconsumeTimes();
if (times > 3){
// 3次都消费失败,记录下来,返回成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
// 业务处理,报错会视为消费失败消息重新回到队列
...
}
});
consumer.start();
顺序模式 MessageListenerOrderly
- 最大重试次数:Integer.MAX_VALUE
- 重试间隔:默认 1000ms(1秒)
- 和并发模式不一样:重试时是在原始队列重试(不会进入专门的重试队列)、不会进入死信队列,会无限次重试,直至成功(会阻塞当前队列)
// 为每个队列维护错误计数器
private ConcurrentMap<Integer, AtomicInteger> queueErrorCounters = new ConcurrentHashMap<>();
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("retryTopic", "*");
// 顺序模式:设置顺序消费挂起时间(毫秒)
consumer.setSuspendCurrentQueueTimeMillis(5000);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
consumeMessage(msgs, context);
}
});
consumer.start();
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
MessageExt msg = msgs.get(0);
int queueId = msg.getQueueId();
try {
// 业务处理
queueErrorCounters.remove(queueId); // 成功则清除计数器
return ConsumeOrderlyStatus.SUCCESS;
} catch (Exception e) {
// 错误计数+1
AtomicInteger counter = queueErrorCounters.computeIfAbsent(queueId, k -> new AtomicInteger(0));
int retryCount = counter.incrementAndGet();
// 超过阈值则跳过(需记录告警)
if(retryCount > MAX_RETRY){
log.error("消息[{}]重试超过{}次,人工介入", msg.getMsgId(), MAX_RETRY);
return ConsumeOrderlyStatus.SUCCESS; // 慎用!可能造成数据不一致
}
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}

浙公网安备 33010602011771号