springboot rabbitMQ 死信队列

死信队列是什么?

死信队列实际上就是,当我们的业务队列处理失败(比如抛异常并且达到了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后,该消息会被放入死信队列进行处理。

我们首先运行消费者的引导类,自动创建相应的交换机和队列。
然后运行生产者的测试类,发送消息
控制台打印结果:
在这里插入图片描述
该条消息进入了死信队列。
此时我们停止消费者,再次发送一条信息。
在这里插入图片描述
发现队列中存在一条消息,十秒钟后(我们设置是十秒),消息转移到死信队列中了
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42494845/article/details/108813060