RocketMQ实战记录
背景
客户需要用企业微信通知客户。
先上代码
下面是异步生产者的代码,第一次发送时会报错,如下所示
2023-12-22 16:46:57.577 INFO 20832 --- [enderExecutor_1] c.c.m.plan.service.WeixinBasicsProducer : 异步发送 - 发送失败
org.apache.rocketmq.client.exception.MQClientException: Send [1] times, still failed, cost [6008]ms, Topic: asyncSend, BrokersSent: [broker-a]
See http://rocketmq.apache.org/docs/faq/ for further details.
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendDefaultImpl(DefaultMQProducerImpl.java:665)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.access$300(DefaultMQProducerImpl.java:97)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl$4.run(DefaultMQProducerImpl.java:511)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendKernelImpl call timeout
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendKernelImpl(DefaultMQProducerImpl.java:832)
at org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendDefaultImpl(DefaultMQProducerImpl.java:584)
... 8 more
package com.chinaztt.mes.plan.service;
import com.alibaba.fastjson.JSONArray;
import com.chinaztt.mes.common.util.JsonUtils;
import com.chinaztt.mes.plan.entity.WeixinLog;
import com.chinaztt.mes.technology.dto.OperationRoleDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
*
* 异步消息
*
*/
@Service
@Slf4j
public class WeixinBasicsProducer {
@Resource
RocketMQTemplate rocketMQTemplate;
@Resource
WeixinLogService weixinLogService;
@Resource
private WeixinBasicsProducerFail weixinBasicsProducerFail;
/**
* 异步消息:异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。
*/
public void async(List<OperationRoleDTO> listMap) {
WeixinLog weixinLog = new WeixinLog();
weixinLog.setType("sentRktMq");
weixinLog.setParameter(JsonUtils.toJsonArray(listMap).toString());
weixinLogService.save(weixinLog);
String text = "基本信息案例-异步发送" + System.currentTimeMillis();
log.info(text);
for (int a = 0; a < listMap.size(); a++) {
OperationRoleDTO operationRoleDTO = listMap.get(a);
rocketMQTemplate.asyncSend("asyncSend", listMap.get(a), new SendCallback() {
// SendCallback接收异步返回结果的回调
// 成功发送
@Override
public void onSuccess(SendResult sendResult) {
log.info("异步发送 - 发送成功");
}
// 发送失败
@Override
public void onException(Throwable throwable) {
log.info("异步发送 - 发送失败");
throwable.printStackTrace();
//异步发送者失败后,会再次发送一次
weixinBasicsProducerFail.sent(operationRoleDTO);
}
});
}
log.info("异步发送-已发送...");
}
}
异步发送者失败后,会再次发送一次,调用一下代码
package com.chinaztt.mes.plan.service;
import com.chinaztt.mes.technology.dto.OperationRoleDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
*
* 当发送失败消息时,再次发送---异步消息
*
*/
@Service
@Slf4j
public class WeixinBasicsProducerFail {
@Resource
RocketMQTemplate rocketMQTemplate;
public void sent(OperationRoleDTO operationRoleDTO){
rocketMQTemplate.asyncSend("asyncSendFail", operationRoleDTO, new SendCallback() {
// SendCallback接收异步返回结果的回调
// 成功发送
@Override
public void onSuccess(SendResult sendResult) {
log.info("异步发送 - 发送成功");
}
// 发送失败
@Override
public void onException(Throwable throwable) {
log.info("异步发送 - 发送失败");
throwable.printStackTrace();
}
});
}
}
下面开始消费者代码
package com.chinaztt.mes.plan.service;
import com.chinaztt.mes.common.util.JsonUtils;
import com.chinaztt.mes.plan.entity.WeixinLog;
import com.chinaztt.mes.technology.dto.OperationRoleDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 异步消息
*/
@Component
@RocketMQMessageListener(selectorExpression = "", topic = "asyncSend", consumerGroup = "asyncSend")
@Slf4j
public class WeixinAsyncConsumer implements RocketMQListener<OperationRoleDTO> {
@Resource
WeixinLogService weixinLogService;
@Resource
WeixinBasicsProducerFail weixinBasicsProducerFail;
@Override
public void onMessage(OperationRoleDTO map) {
Integer integer = 0;
//上线后解开
//integer = SendWxCpMsgUtil.sendToWxCp(map.getUsername(), map.getMessage());
if (integer != 1) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
weixinBasicsProducerFail.sent(map);
}
WeixinLog weixinLog = new WeixinLog();
weixinLog.setType("sentWeixin");
weixinLog.setParameter(JsonUtils.toJsonObject(map).toString());
weixinLog.setResult(String.valueOf(integer));
weixinLogService.save(weixinLog);
log.info("异步消息-接受到消息: {}{}", map.getUsername(), map.getMessage());
}
}
下面是异步消费者的代码
package com.chinaztt.mes.plan.service;
import com.chinaztt.mes.common.util.JsonUtils;
import com.chinaztt.mes.plan.entity.WeixinLog;
import com.chinaztt.mes.technology.dto.OperationRoleDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 异步消息
*/
@Component
@RocketMQMessageListener(selectorExpression = "", topic = "asyncSendFail", consumerGroup = "asyncSendFail")
@Slf4j
public class WeixinAsyncConsumerFail implements RocketMQListener<OperationRoleDTO> {
@Resource
WeixinLogService weixinLogService;
@Override
public void onMessage(OperationRoleDTO map) {
Integer integer = 0;
//上线后解开
//integer = SendWxCpMsgUtil.sendToWxCp(map.getUsername(), map.getMessage());
WeixinLog weixinLog = new WeixinLog();
weixinLog.setType("sentWeixin");
weixinLog.setParameter(JsonUtils.toJsonObject(map).toString());
weixinLog.setResult(String.valueOf(integer));
weixinLogService.save(weixinLog);
log.info("异步消息-接受到消息: {}{}", map.getUsername(), map.getMessage());
}
}
下面是生产者的重试机制
package com.chinaztt.mes.plan.service;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
/**
* Producer,发送消息
*/
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("group_name");
producer.setNamesrvAddr("192.168.2.222:9876;192.168.2.223:9876");
producer.setRetryTimesWhenSendFailed(3);
producer.start();
for (int i = 0; i < 100; i++) {
try {
Message msg = new Message("TopicTest", // topic
"TagA", // tag
("HelloWorld - RocketMQ" + i).getBytes() // body
);
SendResult sendResult = producer.send(msg, 1000);
System.out.println(sendResult);
} catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000);
}
}
producer.shutdown();
}
}
下面是消费者的重试代码
package com.chinaztt.mes.plan.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
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;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @Description:RocketMQ消息消费者
*/
@Slf4j
@Service
public class MessageConsumer implements MessageListenerConcurrently {
/* @Value("${spring.rocketmq.namesrvAddr}")
private String namesrvAddr;*/
private final DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DefaultConsumer");
@PostConstruct
public void start() {
try {
consumer.setNamesrvAddr("10.1.200.45:9876");
//从消息队列头部开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//设置集群消费模式
consumer.setMessageModel(MessageModel.CLUSTERING);
//订阅主题
consumer.subscribe("asyncSendwew", "*");
//注册消息监听器
consumer.registerMessageListener(this);
//启动消费端
consumer.start();
log.info("Message Consumer Start...");
System.err.println("Message Consumer Start...");
} catch (MQClientException e) {
log.error("Message Consumer Start Error!!",e);
}
}
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
if (CollectionUtils.isEmpty(msgs)) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
MessageExt message = msgs.get(0);
try {
//逐条消费
String messageBody = new String(message.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.err.println("Message Consumer: Handle New Message: messageId: " + message.getMsgId() + ",topic: " +
message.getTopic() + ",tags: " + message.getTags() + ",messageBody: " + messageBody);
//模拟业务异常
int i = 1 / 0;
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
log.error("Consume Message Error!!", e);
//抛出异常时,返回ConsumeConcurrentlyStatus.RECONSUME_LATER,尝试重试。当重试指定次数后返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS
int reconsumeTimes = message.getReconsumeTimes();
System.err.println("Now Retry Times: " + reconsumeTimes);
if (reconsumeTimes >= 18) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}