目录
RabbitMQ实现延时队列
RabbitMQ实现延时队列有两种方式:1.死信队列 2.下载插件。
Maven依赖
Maven依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置:
spring.rabbitmq.host = 127.0.0.1
spring.rabbitmq.port = 5672
spring.rabbitmq.username = guest
spring.rabbitmq.password = guest
spring.rabbitmq.virtualHost = sandbox
# 手动提交
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=3
spring.rabbitmq.listener.simple.max-concurrency=10
spring.rabbitmq.listener.simple.prefetch=1
spring.rabbitmq.cache.channel.size=50
代码实现
1.插件方式
默认已经安装好RabbitMQ的延时插件,接下来我们直接步入主题。
(1)配置类:配置交换机、延时队列、绑定交换机&队列。
package com.atta.msgdactuator.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* author: cms
* date: 2022/9/27 6:35 PM
* description: xxx
*/
@Configuration
public class SendMailDelayMonitorConfig {
public final static String SEND_MAIL_DELAY_EXCHANGE = "edm.send.mail.delay.exchange";
public final static String SEND_MAIL_DELAY_QUEUE = "edm.send.mail.delay.queue";
public final static String SEND_MAIL_DELAY_ROUTING_KEY = "edm.send.mail.routing.key";
@Bean
public CustomExchange delayMessageExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
/* 自定义交换机*/
return new CustomExchange(
SEND_MAIL_DELAY_EXCHANGE, "x-delayed-message",
true, false, args);
}
@Bean
public Queue delayMessageQueue() {
return new Queue(SEND_MAIL_DELAY_QUEUE,
true,
false,
false);
}
@Bean
public Binding bindingDelayExchangeAndQueue() {
return BindingBuilder.bind(delayMessageQueue())
.to(delayMessageExchange())
.with(SEND_MAIL_DELAY_ROUTING_KEY)
.noargs();
}
}
(2)生产者
package com.atta.msgdactuator.notification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* author: cms
* date: 2022/9/27 6:00 PM
* description: 发送延时消息。
*/
@Slf4j
@Component
public class SendMailDelayMonitorProduce {
// 此实例是公司组件的rabbitTemplate,如果想自己绑定confirm、return事件,可将rabbitTemplate实例设置为scope。
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 功能描述:发送延时消息。
* 异常声明:由调用者捕获。
*
* @param message 消息内容
* @param delayMs 延迟时间 单位:毫秒
* @param correlationData 全局唯一id
* @param exchange 交换机
* @param routingKey 消息路由键
* @return
*/
public void sendDelayMq(Object message,
Integer delayMs,
CorrelationData correlationData,
String exchange,
String routingKey) {
log.info("[SendMailDelayHandleProduce#sendDelayMq] REQ. start to send mq. msg:[{}]", message);
if (delayMs == null) {
delayMs = 0;
}
// 发送消息时指定 header 延迟时间
Integer finalDelayMs = delayMs;
rabbitTemplate.convertAndSend(
exchange,
routingKey,
message,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 设置消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setDelay(finalDelayMs);
return message;
}
}, correlationData);
log.info("[SendMailDelayHandleProduce#sendDelayMq] END. finish to send mq. msg:[{}]", message);
}
}
(3)消费者
package com.atta.msgdactuator.listen;
import com.atta.infra.common.api.model.Mid;
import com.atta.infra.msgdactuator.api.enums.MessageSendRequestStatus;
import com.atta.infra.utils.AssertUtil;
import com.atta.infra.utils.JsonUtils;
import com.atta.msgdactuator.config.SendMailDelayMonitorConfig;
import com.atta.msgdactuator.dal.dao.MessageSendRequestDAO;
import com.atta.msgdactuator.dal.dataobject.MessageSendRequestDO;
import com.atta.msgdactuator.enums.TaskProcessType;
import com.atta.msgdactuator.model.send.SendMailDelayModel;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
/**
* author: cms
* date: 2022/9/27 6:40 PM
* description: 监听延时消息,并做告警。
*/
@Component
@Slf4j
public class SendMailDelayMonitorConsumer {
@Autowired
private MessageSendRequestDAO messageSendRequestDAO;
@RabbitListener(queues = SendMailDelayMonitorConfig.SEND_MAIL_DELAY_QUEUE)
@RabbitHandler
public void handle(Message msg, Channel channel) {
String msgID = "";
long deliveryTag = msg.getMessageProperties().getDeliveryTag();
try {
/* 获取关联性数据,发送的时候设设置的(使用关联性数据来做msgID)*/
Object correlationObj = msg.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
if(correlationObj != null){
msgID = correlationObj.toString();
}
String msgContent = new String(msg.getBody(), Charset.defaultCharset());
log.info("[SendMailDelayHandleConsumer#handle] Received msgID:[{}]," +
" deliveryTag:[{}], msgContent:[{}]", msgID, deliveryTag, msgContent);
/* 业务逻辑处理*/
doHandler(msgContent);
/* 手动ACK*/
channel.basicAck(deliveryTag, false);
log.info("[SendMailDelayHandleConsumer#onDelayMessage] Handler Succeeded msgID:[{}]", msgID);
} catch (Exception e) {
log.error("[SendMailDelayHandleConsumer#handler] ERROR msgID:[{}]", msgID, e);
}
}
private void doHandler(String msgContent) {
AssertUtil.notBlank(msgContent, "msgContent is null or empty");
SendMailDelayModel sendMailDelayModel = JsonUtils.parseObject(msgContent, SendMailDelayModel.class);
String type = sendMailDelayModel.getType();
String taskId = sendMailDelayModel.getTaskId();
String firmId = sendMailDelayModel.getFirmId();
Mid mid = sendMailDelayModel.getMid();
log.info("[SendMailDelayHandleConsumer#doHandler]. taskId:[{}], type:[{}], firmId:[{}], mid:[{}]", taskId, type, firmId, mid);
MessageSendRequestDO message = messageSendRequestDAO.selectByTaskId(mid, taskId);
AssertUtil.notNull(message, "[SendMailDelayHandleConsumer#doHandler]. messageSendRequestDO is null. taskId:[" + taskId,"], mid:[" + mid +"]");
MessageSendRequestStatus status = message.getStatus();
log.info("[SendMailDelayHandleConsumer#doHandler]. taskId:[{}], status:[{}], firmId:[{}], mid:[{}]", taskId, status, firmId, message.getMid());
// 以下日志接入钉钉报警
if (TaskProcessType.TASK_PROCESS_OF_ENGINE.getType().equals(type) && !MessageSendRequestStatus.COMPLETED.equals(status)){
log.error("[SendMailDelayHandleConsumer#doHandler]. Task timeout notify. 【引擎端】任务处理超时报警! taskId:[{}], mid:[{}], firmId:[{}], currentStatus:[{}]",
taskId, mid, firmId, status);
}else if(TaskProcessType.TASK_PROCESS_OF_CHANNEL.getType().equals(type) && !MessageSendRequestStatus.FINISHED.equals(status)){
log.error("[SendMailDelayHandleConsumer#doHandler]. Task timeout notify. 【渠道端】任务处理超时报警! taskId:[{}], mid:[{}], firmId:[{}], currentStatus:[{}]",
taskId, mid, firmId, status);
}else {
log.info("[SendMailDelayHandleConsumer#doHandler]. Task process finish. taskId:[{}], mid:[{}], firmId:[{}], currentStatus:[{}]",
taskId, mid, firmId, status);
}
}
}
(4)发送消息
public void sendMailDelayMonitor(MessageSendRequestDO request, TaskProcessType taskProcessType, Integer delayTimeMinute){
try {
AssertUtil.notNull(delayTimeMinute, "[MessageSendRequestServiceImpl#sendMailDelayMonitor] ERROR. delayTimeMinute is null.");
Integer delayTImeMs = delayTimeMinute * 60 * 1000;
String mqMessageId = IdWorker.getId() + "";
SendMailDelayModel message = SendMailDelayModel.builder()
.type(taskProcessType.getType())
.taskId(request.getTaskId())
.firmId(request.getFirmId())
.mid(request.getMid()).build();
String messageJsonStr = JsonUtils.toJSONString(message);
sendMailDelayMonitorProduce.sendDelayMq(
messageJsonStr,
delayTImeMs,
new CorrelationData(mqMessageId),
SendMailDelayMonitorConfig.SEND_MAIL_DELAY_EXCHANGE,
SendMailDelayMonitorConfig.SEND_MAIL_DELAY_ROUTING_KEY
);
}catch (Exception e) {
log.error("[SendEmail#send] ERROR. fail to record [channel] task start time. taskId:[{}], mid:[{}], requestId:[{}]",
request.getTaskId(), request.getMid(), request.getRequestId(), e);
}
}