RabbitMQ死信队列
本文不介绍如何整合消息队列,有需要请参考我之前发布:https://blog.csdn.net/a870368162/article/details/99566685
git链接:https://github.com/a870368162/SpringBoot-RabbitMQ
RabbitMQ要实现延时任务,需要使用RabbitMQ的死信交换机(Exchange)和消息的存活时间TTL(Time To Live)来实现
死信交换机
死信交换机跟普通交换机一样,只是这个交换机用来存放过期的消息
- 当一个消息没有相对应的消费者对其进行消费, 并且消息设置了TTL,消息过期后会进入死信交换机。
- 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。
- 当消息进入死信交换机后,死信交换机在把消息转发给专门处理死信消息的消费者,及可实现定时任务
设置消息TTL(消息存活时间)
- 消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。
- 可以通过设置消息的expiration字段属性来设置时间,代码如下:
MessageProperties messageProperties = new MessageProperties();
//这里设置消息过期的时间
messageProperties.setExpiration(60+ "000");
messageProperties.setCorrelationIdString(serialClient.getGlobalSerial().toString());
Message message = new Message(JSONObject.toJSONString(这里写你要发送给队列的消息).getBytes(), messageProperties);
// 消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//发送消息给死信队列
this.send.beadSend(RabbitMQConstant.TOPIC_ROUTINGKEY1, message);
当上面的消息扔到队列中后,过了60秒,如果没有被消费,它就会发送到死信交换机。
创建死信交换机, 死信队列以及处理死信消息的消费者
实现代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.DelayQueue;
/**
* @消息队列配置
* @Autor zxf
* @Date 2019/8/15
*/
@Configuration
public class QueueConfig {
private static final Logger logger = LoggerFactory.getLogger(QueueConfig.class);
/**
* 创建订阅模式交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(RabbitMQConstant.FANOUT_EXCHANGE,true, false);
}
/**
* 创建路由模式交换机
* @return
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange(RabbitMQConstant.DIRECT_EXCHANGE,true, false);
}
/**
* 创建主题模式交换机
* @return
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(RabbitMQConstant.TOPIC_EXCHANGE,true, false);
}
/**
* 创建死信交换机,跟普通交换机一样,只是死信交换机只用来接收过期的消息
* @return
*/
@Bean
public DirectExchange deadExchange() {
return new DirectExchange(RabbitMQConstant.DEAD_EXCHANGE, true, false);
}
/**
* 创建死信队列,该队列没有消费者,消息会设置过期时间,消息过期后会发送到死信交换机,在由死信交换机转发至处理该消息的队列中
* @return
*/
@Bean
public Queue BeadQueue() {
Map<String, Object> arguments = new HashMap<>(2);
// 死信路由到死信交换器DLX
arguments.put("x-dead-letter-exchange", RabbitMQConstant.DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key", RabbitMQConstant.ROUTING_KEY2);
return new Queue(RabbitMQConstant.DEAD_QUEUE, true, false, false, arguments);
}
/**
* 处理死信队列的消费队列
*
*/
@Bean
public Queue consumerBeadQueue() {
return new Queue(RabbitMQConstant.CONSUMER_BEAD_QUEUE, true); // 队列持久
}
/**
* 创建队列1
* @return
*/
@Bean
public Queue Queue1() {
//队列持久化
return new Queue(RabbitMQConstant.QUEUE_1, true);
}
/**
* 创建队列2
* @return
*/
@Bean
public Queue Queue2() {
return new Queue(RabbitMQConstant.QUEUE_2, true);
}
/**
* 订阅模式队列1绑定交换机
* @return
*/
@Bean
public Binding fanoutBinding1() {
return BindingBuilder.bind(Queue1()).to(fanoutExchange());
}
/**
* 订阅模式队列2绑定交换机
* @return
*/
@Bean
public Binding fanoutBinding2() {
return BindingBuilder.bind(Queue2()).to(fanoutExchange());
}
/**
* 路由模式队列1绑定交换机,通过key1发送
* @return
*/
@Bean
public Binding directBinding1() {
return BindingBuilder.bind(Queue1()).to(directExchange()).with(RabbitMQConstant.ROUTING_KEY1);
}
/**
* 路由模式队列2绑定交换机,通过key2发送
* @return
*/
@Bean
public Binding directBinding2() {
return BindingBuilder.bind(Queue2()).to(directExchange()).with(RabbitMQConstant.ROUTING_KEY2);
}
/**
* 主题模式队列1绑定交换机
* 符号“#”匹配一个或多个词,符号“*”匹配一个词。比如“hello.#”能够匹配到“hello.123.456”,但是“hello.*”只能匹配到“hello.123”
* @return
*/
@Bean
public Binding topicBinding1() {
return BindingBuilder.bind(Queue1()).to(topicExchange()).with(RabbitMQConstant.TOPIC_ROUTINGKEY1);
}
/**
* 主题模式队列1绑定交换机
* 符号“#”匹配一个或多个词,符号“*”匹配一个词。比如“hello.#”能够匹配到“hello.123.456”,但是“hello.*”只能匹配到“hello.123”
* @return
*/
@Bean
public Binding topicBinding2() {
return BindingBuilder.bind(Queue2()).to(topicExchange()).with(RabbitMQConstant.TOPIC_ROUTINGKEY2);
}
/**
* 将死信队列与死信交换机绑定,key1
*
* @return
*/
@Bean
public Binding beadQueuebinding() {
return BindingBuilder.bind(BeadQueue()).to(deadExchange()).with(RabbitMQConstant.ROUTING_KEY1);
}
/**
* 将处理死信队列的消费队列与死信交换机绑定 key2
*
* @return
*/
@Bean
public Binding consumerBeadQueuebinding() {
return BindingBuilder.bind(consumerBeadQueue()).to(deadExchange()).with(RabbitMQConstant.ROUTING_KEY2);
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
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;
/**
* @消息队列发送工具类
* @Autor zxf
* @Date 2019/8/15
*/
@Component
public class Send implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
private static final Logger log = LoggerFactory.getLogger(Send.class);
private RabbitTemplate rabbitTemplate;
@Autowired
public Send(RabbitTemplate rabbitTemplate) {
super();
this.rabbitTemplate = rabbitTemplate;
this.rabbitTemplate.setMandatory(true);
this.rabbitTemplate.setReturnCallback(this);
this.rabbitTemplate.setConfirmCallback(this);
}
/**
* 发布/订阅模式发送
* @param json
*/
public void routeSend(String json) {
Message message = this.setMessage(json);
//在fanoutExchange中在绑定Q到X上时,会自动把Q的名字当作bindingKey。
this.rabbitTemplate.convertAndSend(RabbitMQConstant.FANOUT_EXCHANGE, "", message);
}
/**
* 简单模式发送
* @param json
*/
public void simplSend(String json) {
Message message = this.setMessage(json);
this.rabbitTemplate.convertAndSend(RabbitMQConstant.QUEUE_1, message);
}
/**
* 路由模式发送
* @param routingKey
* @param json
*/
public void routingSend(String routingKey, String json) {
Message message = this.setMessage(json);
this.rabbitTemplate.convertAndSend(RabbitMQConstant.DIRECT_EXCHANGE, routingKey, message);
}
/**
* 主题模式发送
*
* @param routingKey
* @param json
*/
public void topicSend(String routingKey, String json) {
Message message = this.setMessage(json);
this.rabbitTemplate.convertAndSend(RabbitMQConstant.TOPIC_EXCHANGE, routingKey, message);
}
/**
* 死信模式发送,用于定时任务处理
* @param routingKey
* @param message
*/
public void beadSend(String routingKey, Message message) {
this.rabbitTemplate.convertAndSend(RabbitMQConstant.DEAD_EXCHANGE, routingKey, message);
}
/**
* 设置消息参数
* @param json
* @return
*/
private Message setMessage(String json){
MessageProperties messageProperties = new MessageProperties();
Message message = new Message(json.getBytes(), messageProperties);
//消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
/**
* 消息确认
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息发送确认成功");
} else {
log.info("消息发送失败:" + cause);
}
}
/**
* 消息发送失败回传
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("return--message:" + new String(message.getBody()) + ",replyCode:" + replyCode + ",replyText:"
+ replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
try {
Thread.sleep(10000L);
// TODO 重新发送消息至队列,此处应写一套重发机制,重发多少次结束,否则如果消息如果一直发送失败,则会一直发下去!
this.rabbitTemplate.convertAndSend(exchange, routingKey, message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
超时未支付取消订单步骤如下:
- 创建订单入库后发送该订单的id到死信队列,设置过期时间(该队列不能有消费者)
- 消息过期后会通过死信交换机发送到相对应的处理队列中
- 从处理队列中获得订单id,修改订单状态为取消