目录
1、什么是死信队列
2、产生死信队列的原因
3、代码实现---直连交换机
3.1、导入依赖
3.2、配置rabbitmq连接信息
3.3、编写配置类
3.4、编写生产者
3.5、编写消费者
3.6、测试
4、死信队列的架构原理
5、死信队列应用场景
RabbitMQ死信队列俗称,备胎队列;消息中间件因为某种原因拒收该消息后,可以转移到死信队列中存放,死信 队列也可以有交换机和路由key等。消费者在消费生产者生产的消息时发生了某些特殊情况(下文会说),导致消息无法被正常消费,存放这些未被消费的消息的队列即为死信队列。
1、消息投递到MQ中存放 消息已经过期 消费者没有及时的获取到我们消息,消息如果存放到mq服务器中过期之后,会转移到备胎死信队列存放。
2、队列达到最大的长度 (队列容器已经满了。
3、消费者消费多次消息失败,就会转移存放到死信队列中。
org.springframework.boot
spring-boot-starter-amqp
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: guest
####密码
password: guest
### 虚拟主机 rabbitmq服务器自己创建
virtual-host: /rkVirtualHost
listener:
simple:
retry:
####开启消费者(程序出现异常的情况下会)进行重试
enabled: true
####最大重试次数 到达最大重试次数会自动进入死信队列,前提是未开启ack手动确认
max-attempts: 3
####重试间隔时间
initial-interval: 3000
#ack 自动ack确认:auto 手动ack确认:manual
acknowledge-mode: auto
server:
port: 8080
###模拟演示死信队列
rk:
dlx:
exchange: rk_dlx_exchange
queue: rk_order_dlx_queue
routingKey: dlx
###备胎交换机
order:
exchange: rk_order_exchange
queue: rk_order_queue
routingKey: order
必须设置为自动ack,到达重试次数后才会自动进入私信队列。 如果使用手动ack,需要参考如下:
RabbitMQ重试机制+死信队列_大锅睿的博客-CSDN博客_mq重试机制
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class DeadLetterMQConfig {
/**
* 订单交换机
*/
@Value("${rk.order.exchange}")
private String orderExchange;
/**
* 订单队列
*/
@Value("${rk.order.queue}")
private String orderQueue;
/**
* 订单路由key
*/
@Value("${rk.order.routingKey}")
private String orderRoutingKey;
/**
* 死信交换机
*/
@Value("${rk.dlx.exchange}")
private String dlxExchange;
/**
* 死信队列
*/
@Value("${rk.dlx.queue}")
private String dlxQueue;
/**
* 死信路由
*/
@Value("${rk.dlx.routingKey}")
private String dlxRoutingKey;
/**
* 声明死信交换机
* @return DirectExchange
*/
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(dlxExchange);
}
/**
* 声明死信队列
* @return Queue
*/
@Bean
public Queue dlxQueue() {
return new Queue(dlxQueue);
}
/**
* 声明订单业务交换机
* @return DirectExchange
*/
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(orderExchange);
}
/**
* 声明订单队列
* @return Queue
*/
@Bean
public Queue orderQueue() {
// 订单队列绑定我们的死信交换机
Map arguments = new HashMap<>(2);
//死信交换机
arguments.put("x-dead-letter-exchange", dlxExchange);
//死信队列
arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
return new Queue(orderQueue, true, false, false, arguments);
}
/**
* 绑定死信队列到死信交换机
* @return Binding
*/
@Bean
public Binding binding(Queue dlxQueue,DirectExchange dlxExchange) {
return BindingBuilder.bind(dlxQueue).to(dlxExchange).with(dlxRoutingKey);
}
/**
* 绑定订单队列到订单交换机
* @return Binding
*/
@Bean
public Binding orderBinding(Queue orderQueue,DirectExchange orderExchange) {
return BindingBuilder.bind(orderQueue).to(orderExchange).with(orderRoutingKey);
}
}
启动后会自动让rabbitmq创建交换机和队列,如果创建失败,一定要去rabbitmq上先删除已有的队列再启动。
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 订单交换机
*/
@Value("${rk.order.exchange}")
private String orderExchange;
/**
* 订单路由key
*/
@Value("${rk.order.routingKey}")
private String orderRoutingKey;
@RequestMapping("/sendOrder")
public String sendOrder() {
String msg = "rk 学 rabbitmq";
//发送消息 参数一:交换机 参数二:路由键(用来指定发送到哪个队列)
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg, message -> {
// 设置消息过期时间 10秒过期 如果过期时间内还没有被消费 就会发送给死信队列
message.getMessageProperties().setExpiration("10000");
return message;
});
return "success";
}
}
订单消费者:
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 订单消费者
*/
@Component
@Slf4j
public class OrderConsumer {
/**
* 监听队列回调的方法
*
* @param msg
*/
@RabbitListener(queues = "rk_order_queue")
public void orderConsumer(String msg) {
log.info(">>正常订单消费者消息MSG:{}<<", msg);
}
}
死信消费者:
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class OrderDlxConsumer {
/**
* 死信队列监听队列回调的方法
*
* @param msg
*/
@RabbitListener(queues = "rk_order_dlx_queue")
public void orderConsumer(String msg) {
log.info(">死信队列消费订单消息:msg{}<<", msg);
}
}
项目启动后,spring创建交换机和队列的bean以及绑定关系。rabbitmq服务器会自动创建交换机和队列。访问生产者接口
因为订单消费者正常,并且能够成功消费,所以消息没有进入死信队列。现在我们注释掉订单消费者,这样10秒内消息无法正常消费,就会进入死信交换机再投递给死信队列,由死信队列消费者进行消费。同样访问接口:
假如有这样一个需要延迟消费的场景,我们可以死信队列来实现延迟队列。
死信队列和普通队列区别不是很大
普通与死信队列都有自己独立的交换机和路由key、队列和消费者。
区别:
1、生产者投递消息先投递到我们普通交换机中,普通交换机在将该消息投到普通队列中缓存起来,普通队列对应有自己独立普通消费者。
2、如果生产者投递消息到普通队列中,普通队列发现该消息一直没有被消费者消费的情况下,在这时候会将该消息转移到死信(备胎)交换机中,死信(备胎)交换机对应有自己独立的 死信(备胎)队列 对应独立死信(备胎)消费者。
30分钟订单超时设计:网购时下了订单30内未付款,订单将会自动取消,进行回滚加 还原库存。
方案一Redis过期key :
在用户下单的时候,会使用redis存入一个key,过期时间为30分钟。当这个key过期之后会发送一个事件通知给客户端。这个时候根据订单的id去查询该订单是否付款,如果没有付款,就进行回滚操作(将库存还原)
方案二死信延迟队列实现:
采用死信队列,创建一个普通队列没有对应的消费者 消费消息,在30分钟过后就会将该消息转移到死信备胎消费者实现消费。备胎死信消费者会根据该订单号码查询是否已经支付过,如果没有支付的情况下则会开始回滚库存操作(进行加库存)