RabbitMq本身是没有延时队列的,它自身是不支持的,但是可以通过别的途径来实现,比如插件或者死信队列(DLX)来实现延迟队列的功能。
首先来说不需要安装插件,使用死信队列的方式来模拟达到延时的效果。具体的原理是:
- 定义一个普通的队列作为延时队列
delay-queue-test, 然后设置消息的过期时间。 - 这个延时队列并没有消费者去消费,主动等待消息过期,之后将消息路由到死信队列
- 消费者从死信队列中获取消息,然后消费,从而达到延时的效果。
具体的实现步骤:
配置死信队列和延时队列
package com.im.chat.mq.config;
import com.im.chat.message.constants.QueueConst;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Title: RabbitmqDelayConfig
* @Package com.im.chat.mq.config
* @Description:
*/
@Configuration
public class RabbitmqDelayConfig {
// 定义延时队列
@Bean
public Queue delayedQueue() {
return QueueBuilder.durable(QueueConst.DeadLetter.QUEUE_DELAY_30000)
.withArgument("x-dead-letter-exchange", QueueConst.DIRECT_DELAY_EXCHANGE) // 死信交换机
.withArgument("x-dead-letter-routing-key", QueueConst.DeadLetter.ROUTE_KEY_ORDER) // 死信路由键
.withArgument("x-message-ttl", 30000) // 消息 TTL(单位:毫秒)
.build();
}
// 定义死信队列
@Bean
public Queue deadLetterQueue() {
return new Queue(QueueConst.DeadLetter.QUEUE_ORDER, true); // 持久化队列
}
// 定义死信交换机
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange(QueueConst.DIRECT_DELAY_EXCHANGE, true, false);
}
// 绑定死信队列到死信交换机
@Bean
public Binding bindingDeadLetterQueue(Queue deadLetterQueue, @Qualifier("deadLetterExchange") DirectExchange deadLetterExchange) {
return BindingBuilder.bind(deadLetterQueue)
.to(deadLetterExchange)
.with(QueueConst.DeadLetter.ROUTE_KEY_ORDER);
}
}
发送消息到延时队列
package com.im.chat.mq.producer;
import com.im.chat.message.constants.QueueConst;
import com.im.chat.mq.TestMsgRecord;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
/**
* @Title: DelayedMessageProducer
* @Package com.im.chat.mq.producer
* @Description:
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class DelayedMessageProducer {
private final RabbitTemplate rabbitTemplate;
public void sendDelayedMessage(String message) {
log.info("发送消息:{}", message);
TestMsgRecord testMsgRecord = new TestMsgRecord(message);
rabbitTemplate.convertAndSend(QueueConst.DeadLetter.QUEUE_DELAY_30000, testMsgRecord);
}
}
消费死信队列的消息
package com.im.chat.mq.consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.im.chat.message.constants.QueueConst;
import com.im.chat.mq.TestMsgRecord;
import com.rabbitmq.client.Channel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @Title: DeadLetterQueueConsumer
* @Package com.im.chat.mq.consumer
* @Description:
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class DeadLetterQueueConsumer {
private final ObjectMapper objectMapper;
@RabbitListener(queues = QueueConst.DeadLetter.QUEUE_ORDER)
public void onMessage(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
TestMsgRecord testMsgRecord = objectMapper.readValue(message.getBody(), TestMsgRecord.class);
log.info("死信队列消费消息 {}", testMsgRecord);
channel.basicAck(tag, false);
}
}
下面是安装插件来实现延时队列的效果
具体步骤安装步骤
- 去Rabbitmq官网搜索rabbitmq-delayed-message-exchange 这个插件 rabbitmq-delayed-message-exchange, 根据安装的Rabbitmq版本找到对应的版本
-
将下载的插件解压放入Rabbitmq的插件目录。比如
/usr/lib/rabbitmq/lib/rabbitmq_server-<version>/plugins -
命令行启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange -
重启Rabbitmq让其生效
systemctl restart rabbitmq-server
定义延时交换机
package com.im.chat.mq.config;
import com.im.chat.message.constants.QueueConst;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Title: RabbitmqDelayConfig
* @Package com.im.chat.mq.config
* @Description:
*/
@Configuration
public class RabbitmqDelayPluginsConfig {
// 定义延时交换机
@Bean(value = "delayedPluginsExchange")
public DirectExchange delayedPluginsExchange() {
return ExchangeBuilder
.directExchange(QueueConst.DIRECT_DELAY_PLUGINS_EXCHANGE)
.delayed()
.durable(true).build();
}
// 绑定队列到延时交换机
@Bean
public Declarables bindOrderAdvance(@Qualifier("delayedPluginsExchange") Exchange delayedPluginsExchange) {
Queue queue = new Queue(QueueConst.DelayOrder.QUEUE, true, false, false, null);
Binding binding = BindingBuilder.bind(queue).to(delayedPluginsExchange).with(QueueConst.DelayOrder.ROUTE_KEY).noargs();
return new Declarables(queue, binding);
}
}
发送延时消息
public class TestDelayMsg {
private String msg;
private String time;
private Integer expired;
public TestDelayMsg() {
}
public TestDelayMsg(String msg, String time, Integer expired) {
this.expired = expired;
this.msg = msg;
this.time = time;
}
public Integer getExpired() {
return expired;
}
public void setExpired(Integer expired) {
this.expired = expired;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
@Override
public String toString() {
return "TestDelayMsg{" +
"expired=" + expired +
", msg='" + msg + ''' +
", time='" + time + ''' +
'}';
}
}
public void sendDelayedPluginsMessage(String message, int num) {
log.info("发送消息:{}", message);
String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
TestDelayMsg testMsgRecord = new TestDelayMsg(message, format, 1000 * num);
HashMap<String, Object> map = new HashMap<>(3);
map.put("x-delay", 1000 * num);
map.put("x-retryCount", 3);
map.put("x-timestamp", format);
rabbitTemplate.convertAndSend(QueueConst.DIRECT_DELAY_PLUGINS_EXCHANGE, QueueConst.DelayOrder.ROUTE_KEY,
testMsgRecord, m -> {
map.forEach((k, v) -> m.getMessageProperties().setHeader(k, v));
return m;
}, new CorrelationData());
}
消费者监听
package com.im.chat.mq.consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.im.chat.message.constants.QueueConst;
import com.im.chat.mq.TestDelayMsg;
import com.rabbitmq.client.Channel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
/**
* @Title: DelayQueueConsumer
* @Package com.im.chat.mq.consumer
* @Description:
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class DelayQueueConsumer {
private final ObjectMapper objectMapper;
@RabbitListener(queues = QueueConst.DelayOrder.QUEUE)
public void onMessage(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
TestDelayMsg testMsgRecord = objectMapper.readValue(message.getBody(), TestDelayMsg.class);
MessageProperties messageProperties = message.getMessageProperties();
Map<String, Object> headers = messageProperties.getHeaders();
// headers.forEach((k, v) -> {
// log.info("消息头信息 key: {}, value: {}", k, v);
// });
log.info("延迟队列: 消费时间{} 消费消息 {}", LocalDateTime.now(), testMsgRecord);
channel.basicAck(tag, false);
} catch (Exception e) {
channel.basicNack(tag, false, false);
log.error("MQ消费失败", e);
}
}
}