死信队列是什么?
死信队列实际上就是,当我们的业务队列处理失败(比如抛异常并且达到了retry的上限),就会将消息重新投递到另一个Exchange(Dead Letter Exchanges),该Exchange再根据routingKey重定向到另一个队列,在这个队列重新处理该消息。
本文通过springboot 集成rabbitMQ,配置一个正常队列和一个死信队列,正常队列设置了消息最长存活时间,当队列中的消息超过存活时间且还没有被消费时,该消息就会存入死信队列。
生产者
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sunyuqi</groupId>
<artifactId>com.sunyuqi</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.37</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
spring.rabbitmq.host=192.168.130.128
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
##开启消息确认机制
#spring.rabbitmq.publisher-confirms=true
#spring.rabbitmq.listener.direct.acknowledge-mode=manual
#设置交换器名称
mq.config.exchange=direct_exchange
#设置队列的路由键
mq.config.queue.routing.key=test_key
实体类
package com.sunyuqi.entity;
import java.io.Serializable;
public class Student implements Serializable {
private Long id;
private String name;
private Integer age;
private String sex;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
生产者类
package com.sunyuqi.routing;
import com.sunyuqi.entity.Student;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ProducerApp {
@Autowired
private RabbitTemplate rabbitTemplate;
//exChange 交换器
@Value("${mq.config.exchange}")
private String exChange;
//routingkey 路由键
@Value("${mq.config.queue.routing.key}")
private String routingKey;
/**
* 发送消息的方法
* @param msg
*/
public void send(String msg){
this.rabbitTemplate.convertAndSend(exChange,routingKey,msg);
}
public void send(Student student){
this.rabbitTemplate.convertAndSend(exChange,routingKey,student);
}
public void send(Message msg){
this.rabbitTemplate.convertAndSend(exChange,routingKey,msg);
}
}
引导类
package com.sunyuqi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
测试类,发送消息
package sunyuqi;
import com.alibaba.fastjson.JSON;
import com.sunyuqi.SpringbootdemoApplication;
import com.sunyuqi.entity.Student;
import com.sunyuqi.routing.ProducerApp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class QueueTest {
@Autowired
private ProducerApp producerApp;
@Test
public void test() throws InterruptedException {
Student student = new Student();
student.setId(1L);
student.setAge(18);
student.setName("jac");
student.setSex("男");
String s = JSON.toJSONString(student);
Message message = MessageBuilder.withBody(s.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8").build();
producerApp.send(message);
}
}
消费者
pom文件和实体类与生产者相同
配置文件
spring.rabbitmq.host=192.168.130.128
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
#开启消息确认机制
spring.rabbitmq.publisher-confirms=true
#消息监听器类型
spring.rabbitmq.listener.type=direct
#开启手动确认消息
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#设置交换器名称
mq.config.exchange=direct_exchange
#设置队列的路由键
mq.config.queue.routing.key=test_key
#设置队列名称
mq.config.queue=test_queue
#设置死信交换器名称
mq.config.dead_exchange=direct_dead_exchange
#设置死信队列的路由键
mq.config.dead_queue.routing.key=test_dead_key
#设置死信队列名称
mq.config.dead_queue=dead_queue
死信队列
package sunyuqi.routing;
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import sunyuqi.entity.Student;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@Component
@RabbitListener(bindings = {
@QueueBinding(value = @Queue(value = "${mq.config.dead_queue}",autoDelete = "false"),
exchange = @Exchange(value = "${mq.config.dead_exchange}", type = ExchangeTypes.DIRECT,autoDelete = "false"),
key = "${mq.config.dead_queue.routing.key}"),
})
public class DeadConsumerApp {
@RabbitListener(queues = "${mq.config.dead_queue}")
@RabbitHandler
public void process(@Payload Message msg,Channel channel) throws IOException, TimeoutException {
String s = new String(msg.getBody());
Student student = JSON.parseObject(s, Student.class);
System.out.println("进入死信队列的消息:"+student);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),false);
}
}
普通队列(该队列配置了一个正常的交换机同时绑定了死信交换机和死信队列)
package sunyuqi.routing;
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
import sunyuqi.entity.Student;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@Component
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(value = "${mq.config.queue}", autoDelete = "false", arguments = {
//绑定死信交换机
@Argument(name = "x-dead-letter-exchange", value = "${mq.config.dead_exchange}"),
//绑定死信队列
@Argument(name = "x-dead-letter-routing-key", value = "${mq.config.dead_queue.routing.key}"),
//配置最大存活时间
@Argument(name = "x-message-ttl", value = "10000", type = "java.lang.Integer")
}),
//配置普通交换机
exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.DIRECT, autoDelete = "false"),
key = "${mq.config.queue.routing.key}"),
})
public class ConsumerApp {
@RabbitListener(queues = "${mq.config.queue}")
@RabbitHandler
public void process(Message message, Channel channel) throws IOException, TimeoutException {
try {
int a = 1/0;
//手动确认消息正确消费
String msg = new String(message.getBody());
Student student = JSON.parseObject(msg,Student.class);
System.out.println("正常处理消息: "+student);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//手动nack,将消息放入死信队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
消费者代码中我们手动写了一个异常,int a=1/0;
捕获异常后我们手动进行nack后,该消息会被放入死信队列进行处理。
我们首先运行消费者的引导类,自动创建相应的交换机和队列。
然后运行生产者的测试类,发送消息
控制台打印结果:
该条消息进入了死信队列。
此时我们停止消费者,再次发送一条信息。
发现队列中存在一条消息,十秒钟后(我们设置是十秒),消息转移到死信队列中了