简单研究下Springboot 整合RocketMQ。 使用的是Apache的rocketmq-spring-boot-starter
1. 初始化项目
1. pom 文件
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>cn.qz.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-rocketmq-provider</artifactId> <dependencies> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
2. application.properties
server.port=8093
spring.application.name=rocketmq-producer
rocketmq.name-server=192.168.13.111:9876;192.168.13.112:9876
rocketmq.producer.group=my-group1
rocketmq.producer.tls-enable=false
# 指定超时时间,默认是3 s
rocketmq.producer.sendMessageTimeout=300000
rocketmq.consumer.group=my-group1
rocketmq.consumer.topic=spring-string
demo.rocketmq.extNameServer=192.168.13.111:9876;192.168.13.112:9876
2. 代码
1. Application
package cn.qz; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2. 生产者
package cn.qz.producer; import cn.qz.constants.MQConstants; import cn.qz.consumer.ObjectConsumerWithReply; import cn.qz.model.User; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.spring.core.RocketMQLocalRequestCallback; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.apache.rocketmq.spring.support.RocketMQHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @RestController public class TestController { @Autowired private RocketMQTemplate rocketMQTemplate; /** * 如果使用其他数据源的rocketTemplate,需要指定名称 */ @Resource(name = "extRocketMQTemplate") private RocketMQTemplate extRocketMQTemplate; @GetMapping("/test1") public String test1() { sendBatch(); return "test1"; } @GetMapping("/test2") public String test2() { sendString(); return "test2"; } @GetMapping("/test3") public String test3() { sendSelfObject(); return "test3"; } @GetMapping("/test4") public String test4() { testRocketMQTemplateTransaction(); return "test4"; } @GetMapping("/test5") public String test5() { testSendAndReceive(); return "test5"; } @GetMapping("/test6") public String test6() { testSelfAck(); return "test6"; } /** * 测试手动回复应答消息 */ private void testSelfAck() { String stringSelfAck = MQConstants.STRING_SELF_ACK; for (int i = 0; i < 10; i++) { rocketMQTemplate.convertAndSend(stringSelfAck + ":tag" + i, "this is self ack " + i); } } /** * 发送具有回传消息的消息。 要求消费者实现 RocketMQReplyListener 接收并回复消息 * * @see ObjectConsumerWithReply */ private void testSendAndReceive() { String objectRequestTopic = MQConstants.SPRING_RECEIVE_OBJ; // Send request in async mode and receive a reply of User type. rocketMQTemplate.sendAndReceive(objectRequestTopic, new User().setUserAge((byte) 9).setUserName("requestUserName"), new RocketMQLocalRequestCallback<User>() { @Override public void onSuccess(User message) { System.out.printf("send user object and receive %s %n", message.toString()); } @Override public void onException(Throwable e) { e.printStackTrace(); } }, 5000); } private void testRocketMQTemplateTransaction() throws MessagingException { String springTransTopic = MQConstants.SPRING_TRANS_TOPIC; String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 10; i++) { try { Message msg = MessageBuilder.withPayload("rocketMQTemplate transactional message " + i). setHeader(RocketMQHeaders.TRANSACTION_ID, "KEY_" + i).build(); SendResult sendResult = rocketMQTemplate.sendMessageInTransaction( springTransTopic + ":" + tags[i % tags.length], msg, null); System.out.printf("------rocketMQTemplate send Transactional msg body = %s , sendResult=%s %n", msg.getPayload(), sendResult.getSendStatus()); Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } } /** * 发送自定义对象携带自定义的header */ private void sendSelfObject() { Message<User> build = MessageBuilder.withPayload( new User().setUserAge((byte) 21).setUserName("sendSelfObject")) .setHeader("MY_HEADER", "MY_VALUE") .setHeader(RocketMQHeaders.KEYS, "key1") .build(); rocketMQTemplate.syncSend(MQConstants.SELF_TOPIC, build); } private void sendBatch() { /** * 发送批量消息 */ String topic = MQConstants.STRING_BATCH; String tags[] = new String[]{"tag0", "tag1", "tag2", "tag3"}; List<Message> msgs1 = new ArrayList<Message>(); for (int i = 0; i < 10; i++) { msgs1.add(MessageBuilder.withPayload("Hello RocketMQ Batch Msg#" + i) .setHeader(RocketMQHeaders.KEYS, "KEY_" + i) .build()); } SendResult sr = rocketMQTemplate.syncSend(topic, msgs1, 60000); System.out.println("--- Batch messages send result :" + sr); /** * 发送批量有序性消息, 根据key 来选择队列 */ topic = MQConstants.STRING_BATCH_ORDER; for (int q = 0; q < 4; q++) { // send to 4 queues List<Message> msgs = new ArrayList<Message>(); for (int i = 0; i < 10; i++) { int msgIndex = q * 10 + i; String msg = String.format("Hello RocketMQ Batch Msg#%d to queue: %d", msgIndex, q); msgs.add(MessageBuilder.withPayload(msg). setHeader(RocketMQHeaders.KEYS, "KEY_" + msgIndex).build()); } sr = rocketMQTemplate.syncSendOrderly(topic, msgs, q + "", 60000); System.out.println("--- Batch messages orderly to queue :" + sr.getMessageQueue().getQueueId() + " send result :" + sr); } } private void sendString() { String springTopic = MQConstants.STRING_TOPIC; String delayTopic = MQConstants.DELAY_TOPIC; String userTopic = MQConstants.USER_TOPIC; /***********第一种发送方式*********/ // 同步发送消息。默认重复两次。不指定超时时间会拿producer 全局的默认超时时间(默认3s) SendResult sendResult = rocketMQTemplate.syncSend(springTopic, "Hello, World!"); System.out.printf("syncSend1 to topic %s sendResult=%s %n", springTopic, sendResult); // 指定超时时间是10 s sendResult = rocketMQTemplate.syncSend(springTopic, "Hello, World2!", 10 * 1000); System.out.printf("syncSend2 to topic %s sendResult=%s %n", springTopic, sendResult); // 发送消息并且指定tag sendResult = rocketMQTemplate.syncSend(springTopic + ":tag0", "Hello, World! tag0!"); System.out.printf("syncSend1 to topic %s sendResult=%s %n", springTopic, sendResult); // 发送自定义的对象,默认会转为JSON串进行发送 sendResult = rocketMQTemplate.syncSend(userTopic, new User().setUserAge((byte) 18).setUserName("Kitty")); System.out.printf("syncSend3 to topic %s sendResult=%s %n", userTopic, sendResult); // 单方向发送消息 rocketMQTemplate.sendOneWay(springTopic, "Hello, World! sendOneWay!"); // 延迟消息。发送 spring message 对象, 指定超时时间是10 s, 并且指定延迟等级 sendResult = rocketMQTemplate.syncSend(delayTopic, MessageBuilder.withPayload( new User().setUserAge((byte) 21).setUserName("Delay")).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE).build(), 10 * 1000, 3); System.out.printf("syncSend5 to topic %s sendResult=%s %n", delayTopic, sendResult); // 异步发送, 指定回调与超时时间 rocketMQTemplate.asyncSend(springTopic, new User().setUserAge((byte) 180).setUserName("asyncSend"), new SendCallback() { @Override public void onSuccess(SendResult sendResult) { System.out.println("asyncSend onSuccess:" + sendResult); } @Override public void onException(Throwable throwable) { System.out.println("asyncSend error:" + throwable.getMessage()); } }, 10 * 1000); /***********第二种发送方式*********/ // convertAndSend 方法发送,其底层也是调用的syncSend 方法,只是没有返回结果 String stringExtTopic = MQConstants.STRING_EXT_TOPIC; rocketMQTemplate.convertAndSend(stringExtTopic + ":tag0", "I'm from tag0"); System.out.printf("syncSend topic %s tag %s %n", springTopic, "tag0"); rocketMQTemplate.convertAndSend(stringExtTopic + ":tag1", "I'm from tag1"); System.out.printf("syncSend topic %s tag %s %n", springTopic, "tag1"); } }
生产者可以发送简单的字符串消息、顺序消息、异步消息、批量消息、同步消息等类型。
另外也可以发送具有回复的消息,这种消息需要消费者实现RocketMQReplyListener 接口并且对消息进行回复。
对于事务消息的发送是每个RocketTemplate 对应一个事务监听器,需要实现 RocketMQLocalTransactionListener, RocketMQLocalTransactionListener 可以加注解RocketMQTransactionListener 指明是事务消息的监听器。RocketMQLocalTransactionListener 本地事务监听器:
package cn.qz.configuration; import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; import org.apache.rocketmq.spring.support.RocketMQHeaders; import org.springframework.messaging.Message; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * @date 2022/4/19 21:04 * @description 全局的事务监听器,一个MQ是一个。 rocketmqTempplate 所有的 */ // 可以用rocketMQTemplateBeanName 指定spring 容器中其他template 使用的事务监听器 @RocketMQTransactionListener public class TransactionListenerImpl implements RocketMQLocalTransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<String, Integer>(); @Override public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); System.out.printf("#### executeLocalTransaction is executed, msgTransactionId=%s %n", transId); int value = transactionIndex.getAndIncrement(); int status = value % 3; localTrans.put(transId, status); if (status == 0) { // Return local transaction with success(commit), in this case, // this message will not be checked in checkLocalTransaction() System.out.printf(" # COMMIT # Simulating msg %s related local transaction exec succeeded! ### %n", msg.getPayload()); return RocketMQLocalTransactionState.COMMIT; } if (status == 1) { // Return local transaction with failure(rollback) , in this case, // this message will not be checked in checkLocalTransaction() System.out.printf(" # ROLLBACK # Simulating %s related local transaction exec failed! %n", msg.getPayload()); return RocketMQLocalTransactionState.ROLLBACK; } System.out.printf(" # UNKNOW # Simulating %s related local transaction exec UNKNOWN! \n"); return RocketMQLocalTransactionState.UNKNOWN; } @Override public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); RocketMQLocalTransactionState retState = RocketMQLocalTransactionState.COMMIT; Integer status = localTrans.get(transId); if (null != status) { switch (status) { case 0: retState = RocketMQLocalTransactionState.COMMIT; break; case 1: retState = RocketMQLocalTransactionState.ROLLBACK; break; case 2: retState = RocketMQLocalTransactionState.UNKNOWN; break; } } System.out.printf("------ !!! checkLocalTransaction is executed once," + " msgTransactionId=%s, TransactionState=%s status=%s %n", transId, retState, status); return retState; } }
3. 消费者
关于消费者有几个接口:
RocketMQPushConsumerLifecycleListener - 可以指明消费的起始位置
RocketMQReplyListener - 消费并且回复消息
RocketMQListener - 单向的消费消息。
(1) 简单的字符串消费者
package cn.qz.consumer; import cn.qz.constants.MQConstants; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.annotation.ConsumeMode; import org.apache.rocketmq.spring.annotation.MessageModel; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.annotation.SelectorType; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.stereotype.Component; /** * @date 2022/4/19 17:30 * @description RocketMQListener: 接收没回复 */ @Component @Slf4j @RocketMQMessageListener( // nameServer = "192.168.13.101:9876", // 指定其他nameserver topic = MQConstants.STRING_TOPIC, selectorType = SelectorType.TAG, // 默认就是按TAG 过滤 selectorExpression = "tag0||tag1", // 默认是 *, 接收所有的TAG consumeMode = ConsumeMode.CONCURRENTLY, // 默认就是该值。ConsumeMode.ORDERLY和MessageModel.BROADCASTING不能一起设置 messageModel = MessageModel.BROADCASTING, // 默认是集群模式 consumerGroup = MQConstants.STRING_TOPIC + "-consumer2") public class StringConsumer2 implements RocketMQListener<String> { @Override public void onMessage(String message) { log.info("message: {}", message); } }
(2) RocketMQReplyListener 消费且回复消息
package cn.qz.consumer; import cn.qz.constants.MQConstants; import cn.qz.model.User; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQReplyListener; import org.springframework.stereotype.Component; /** * @date 2022/4/20 12:45 * @description */ @Component @Slf4j @RocketMQMessageListener( topic = MQConstants.SPRING_RECEIVE_OBJ, consumerGroup = MQConstants.SPRING_RECEIVE_OBJ + "-consumer") public class ObjectConsumerWithReply implements RocketMQReplyListener<User, User> { @Override public User onMessage(User message) { log.info("message: {}", message); User replyUser = new User().setUserAge((byte) 111).setUserName("replyUsername"); return replyUser; } }
(3) RocketMQPushConsumerLifecycleListener 指定起始位置
package cn.qz.consumer; import cn.qz.constants.MQConstants; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener; import org.springframework.stereotype.Component; /** * @date 2022/4/20 13:50 * @description */ @Component @RocketMQMessageListener(topic = MQConstants.STRING_EXT_TOPIC, selectorExpression = "tag0||tag1", consumerGroup = "${spring.application.name}-message-ext-consumer") public class MessageExtConsumer implements RocketMQListener<MessageExt>, RocketMQPushConsumerLifecycleListener { @Override public void onMessage(MessageExt message) { System.out.printf("------- MessageExtConsumer received message, msgId: %s, body:%s \n", message.getMsgId(), new String(message.getBody())); } @Override public void prepareStart(DefaultMQPushConsumer consumer) { // set consumer consume message from now consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); consumer.setConsumeTimestamp(UtilAll.timeMillisToHumanString3(System.currentTimeMillis())); } }
(4) 关于消息的应答
我们之前的消费者是实现MessageListenerConcurrently 接口,然后返回ConsumeConcurrentlyStatus 。 上面这种方式实际抛出异常之后会自动进行消息重新消费。
package cn.qz.consumer; import cn.qz.constants.MQConstants; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.stereotype.Component; /** * @date 2022/4/20 19:53 * @description 测试手动应答消息,抛出异常就相当于是消息消费失败 */ @RocketMQMessageListener(topic = MQConstants.STRING_SELF_ACK, consumerGroup = MQConstants.STRING_SELF_ACK + "-consumer") @Component @Slf4j public class SelfAckConsumer implements RocketMQListener<MessageExt> { @Override public void onMessage(MessageExt message) { String tags = message.getTags(); if ("tag0".equals(tags)) { // 发生异常,顶层会返回ConsumeConcurrentlyStatus.RECONSUME_LATER。 相当于会重新发送。 throw new RuntimeException("tag0 reconsume"); } else { log.info("tags: {}, message: {}", tags, new String(message.getBody())); } } }
4. 关于数据源的扩展
我们可以基于ExtRocketMQTemplateConfiguration 扩展出其他的rocketmq 数据源,相当于注入多个rocketTemplate, 只是nameServer 和 beanName 不同。
(1) ExtRocketMQTemplate
package cn.qz.ext; import org.apache.rocketmq.spring.annotation.ExtRocketMQTemplateConfiguration; import org.apache.rocketmq.spring.core.RocketMQTemplate; /** * @date 2022/4/19 16:17 * @description 相当于多数据源,指定其他的nameServer。 使用的使用直接注入该对象即可 */ @ExtRocketMQTemplateConfiguration(nameServer = "${demo.rocketmq.extNameServer}") public class ExtRocketMQTemplate extends RocketMQTemplate { }
(2) 本地事务监听器 - 用于事务消息
package cn.qz.ext; import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; import org.springframework.messaging.Message; /** * @date 2022/4/19 21:36 * @description 针对extRocketMQTemplate 使用的事务监听器,执行本地事务以及回查 */ @RocketMQTransactionListener(rocketMQTemplateBeanName = "extRocketMQTemplate") public class ExtTransactionListenerImpl implements RocketMQLocalTransactionListener { @Override public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { System.out.printf("ExtTransactionListenerImpl executeLocalTransaction and return UNKNOWN. \n"); return RocketMQLocalTransactionState.UNKNOWN; } @Override public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { System.out.printf("ExtTransactionListenerImpl checkLocalTransaction and return COMMIT. \n"); return RocketMQLocalTransactionState.COMMIT; } }
(3) 使用
/** * 如果使用其他数据源的rocketTemplate,需要指定名称 */ @Resource(name = "extRocketMQTemplate") private RocketMQTemplate extRocketMQTemplate;
补充:关于消息的应答和回复消息的实现。
org.apache.rocketmq.client.consumer.listener.MessageListener 接口下面衍生出的并发MessageListenerConcurrently 和顺序访问MessageListenerOrderly 的接口。两个接口的实现DefaultMessageListenerConcurrently 和 DefaultMessageListenerOrderly 内部消费消息的时候try...catch 异常进行了捕获,发生异常就返回 ConsumeConcurrentlyStatus.RECONSUME_LATER。
RocketMQReplyListener 回复消息是在其实现的handleMessage 方法处理的。是收到消费者回复的消息之后转换为消息对象org.apache.rocketmq.common.message.Message ,然后进行发送。
1. 继承关系如下:
2. org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.DefaultMessageListenerOrderly#consumeMessage 源码如下:
@SuppressWarnings("unchecked") @Override public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) { for (MessageExt messageExt : msgs) { log.debug("received msg: {}", messageExt); try { long now = System.currentTimeMillis(); handleMessage(messageExt); long costTime = System.currentTimeMillis() - now; log.info("consume {} cost: {} ms", messageExt.getMsgId(), costTime); } catch (Exception e) { log.warn("consume message failed. messageExt:{}", messageExt, e); context.setSuspendCurrentQueueTimeMillis(suspendCurrentQueueTimeMillis); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } } return ConsumeOrderlyStatus.SUCCESS; } } private void handleMessage( MessageExt messageExt) throws MQClientException, RemotingException, InterruptedException { if (rocketMQListener != null) { rocketMQListener.onMessage(doConvertMessage(messageExt)); } else if (rocketMQReplyListener != null) { Object replyContent = rocketMQReplyListener.onMessage(doConvertMessage(messageExt)); Message<?> message = MessageBuilder.withPayload(replyContent).build(); org.apache.rocketmq.common.message.Message replyMessage = MessageUtil.createReplyMessage(messageExt, convertToBytes(message)); consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getDefaultMQProducer().send(replyMessage, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { if (sendResult.getSendStatus() != SendStatus.SEND_OK) { log.error("Consumer replies message failed. SendStatus: {}", sendResult.getSendStatus()); } else { log.info("Consumer replies message success."); } } @Override public void onException(Throwable e) { log.error("Consumer replies message failed. error: {}", e.getLocalizedMessage()); } }); } }
3. org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.DefaultMessageListenerConcurrently#consumeMessage 源码如下
@SuppressWarnings("unchecked") @Override public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) { for (MessageExt messageExt : msgs) { log.debug("received msg: {}", messageExt); try { long now = System.currentTimeMillis(); handleMessage(messageExt); long costTime = System.currentTimeMillis() - now; log.info("consume {} cost: {} ms", messageExt.getMsgId(), costTime); } catch (Exception e) { log.warn("consume message failed. messageExt:{}", messageExt, e); context.setSuspendCurrentQueueTimeMillis(suspendCurrentQueueTimeMillis); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } } return ConsumeOrderlyStatus.SUCCESS; } } private void handleMessage( MessageExt messageExt) throws MQClientException, RemotingException, InterruptedException { if (rocketMQListener != null) { rocketMQListener.onMessage(doConvertMessage(messageExt)); } else if (rocketMQReplyListener != null) { Object replyContent = rocketMQReplyListener.onMessage(doConvertMessage(messageExt)); Message<?> message = MessageBuilder.withPayload(replyContent).build(); org.apache.rocketmq.common.message.Message replyMessage = MessageUtil.createReplyMessage(messageExt, convertToBytes(message)); consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getDefaultMQProducer().send(replyMessage, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { if (sendResult.getSendStatus() != SendStatus.SEND_OK) { log.error("Consumer replies message failed. SendStatus: {}", sendResult.getSendStatus()); } else { log.info("Consumer replies message success."); } } @Override public void onException(Throwable e) { log.error("Consumer replies message failed. error: {}", e.getLocalizedMessage()); } }); } }
参考: https://github.com/apache/rocketmq-spring/tree/master/rocketmq-spring-boot-samples/