SpringBoot整合RabbitMQ (回调函数、死信队列)

导入依赖

<!--引入SpringBoot-->
<parent>
    <groupId> org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
</parent>

<dependencies>
    <!--web基础依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--测试依赖包-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <!--spirngboot集成rabbitmq-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
</dependencies>

启动类

@SpringBootApplication
public class MqApp {

    public static void main(String[] args) {
        SpringApplication.run(MqApp.class);
    }
}

yml配置文件

server:
  port: 8001
spring:
  application:
    name: myDemo
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtualHost: /
    listener:
      simple:
        acknowledge-mode: manual #手动签收
        prefetch: 1
    publisher-confirms: true #消息发送到交换机后的回调
    publisher-returns: true  #消息由交换机发到队列失败后的回调
    template:
      mandatory: true # 必须设置成true 消息路由失败通知监听者,而不是将消息丢弃

配置类

package cn.itsource.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitmqConfig {

    public static final String EXCHANGE_TOPIC_0507 = "exchange_topic_0723";//交换机名称
    public static final String QUEUE_NAME_SMS = "queue_sms";//短信队列名称
    public static final String QUEUE_NAME_EMAIL = "queue_email";//邮件队列名称
    public static final String ROUTINGKEY_QUEUE_SMS = "#.sms";//短信队列的routingkey
    public static final String ROUTINGKEY_QUEUE_EMAIL = "#.email";//邮件队列的routingkey

    //注册交换机
    @Bean
    public Exchange topicExchange(){
        //durable:设置持久化
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_0507).durable(true).build();
    }

    //注册短信队列-用来发短信
    @Bean
    public Queue smsQueue(){
        return new Queue(QUEUE_NAME_SMS, true, false, false);
    }

    //注册邮件队列-用来发邮件
    @Bean
    public Queue emailQueue(){
        return new Queue(QUEUE_NAME_EMAIL, true, false, false);
    }

    //短信队列绑定到交换机
    @Bean
    public Binding bindSmsQueue(){
        return BindingBuilder.bind(smsQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_SMS).noargs();
    }

    //邮件队列绑定到交换机
    @Bean
    public Binding bindEmailQueue(){
        return BindingBuilder.bind(emailQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_EMAIL).noargs();
    }
}

序列化配置类(指定所有消息都以JSON格式存到RabbitMQ里,可以不配)回调补全函数在这里配置

知识点:对象与json互转
对象转json串:JSONObject.toJSONString(对象名)
json串转对象:
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;

/**
 * @description: 做序列化
 */
@Configuration
public class RabbitMQConverterConfig implements RabbitListenerConfigurer{

    //以下配置RabbitMQ消息服务
    @Autowired
    public ConnectionFactory connectionFactory;

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        // 这里的转换器设置实现了 通过 @Payload 注解 自动反序列化message body
        factory.setMessageConverter(new MappingJackson2MessageConverter());
        return factory;
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        //设置手动签收
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return factory;
    }

    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        // 这里的转换器设置实现了发送消息时自动序列化消息对象为message body
        template.setMessageConverter(jsonMessageConverter());
        template.setMandatory(true);
        return template;
    }
}

生产者

//RabbitMQ常量类
    public class RabbitMQConstants{
        public static final String QUEUE_NAME_SMS = "queue_sms";//短信队列名称
        public static final String QUEUE_NAME_EMAIL = "queue_email";//邮件队列名称
        public static final String QUEUE_NAME_STATION = "queue_station";//站内信队列名称
    }
package cn.itsource.sender;

import cn.itsource.MqApp;
import cn.itsource.config.RabbitmqConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @description: 生产者
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MqApp.class)
public class Sender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //发送消息
    @Test
    public void send(){
        //发短信消息
        rabbitTemplate.convertAndSend(RabbitMQConstants.QUEUE_NAME_SMS, "course.sms", "号外号外,最新的Java架构师课程上线了,9.9元即可试听哦");

        //发邮件消息
        rabbitTemplate.convertAndSend(RabbitMQConstants.QUEUE_NAME_EMAIL, "course.email", "号外号外,最新的Java架构师课程上线了,9.9元即可试听哦");
    }
}

消费者

/**
 * @description: 消费者
 */
@Component
public class Consumer {

    /**
     * 监听某个队列
     * queues:监听队列的名字,可以写多个
     */
    @RabbitListener(queues = {RabbitmqConfig.QUEUE_NAME_SMS})
    public void handlerSMS(String content, Message message, Channel channel) throws IOException {
        System.out.println("拿到短信消息:" + content);
        //手动签收消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    /**
     * 监听某个队列
     * queues:监听队列的名字,可以写多个
     */
    @RabbitListener(queues = {RabbitmqConfig.QUEUE_NAME_EMAIL})
    public void handlerEmail(String content, Message message, Channel channel) throws IOException {
        System.out.println("拿到邮件消息:" + content);
        //手动签收消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

回调补全

在生产者端,消息投递到交换机失败了 进行回调处理(保证消息不丢失)
这样的回调处理只需要写一次就可以了,所以我们就写到RabbitMQConverterConfig这个配置类里面,rabbitTemplate方法改动如下

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;

/**
 * @description: 做序列化
 */
@Configuration
public class RabbitMQConverterConfig implements RabbitListenerConfigurer{

    //以下配置RabbitMQ消息服务
    @Autowired
    public ConnectionFactory connectionFactory;

    public int retry_count = 3;//最大重试次数

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        // 这里的转换器设置实现了 通过 @Payload 注解 自动反序列化message body
        factory.setMessageConverter(new MappingJackson2MessageConverter());
        return factory;
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        //设置手动签收
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return factory;
    }

    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        // 这里的转换器设置实现了发送消息时自动序列化消息对象为message body
        template.setMessageConverter(jsonMessageConverter());
        template.setMandatory(true);

        //设置消息发送到交换机的回调
        /**
         * 不管消息发送到交换机是否成功,该方法都会被回调
         * @param correlationData:相关数据,发送的时候可以指定一个correlationData,回调的时候传回来给我
         * @param ack:true表示消息发到交换机成功,false表示消息发送交换机失败
         * @param cause:失败原因
         */
        template.setConfirmCallback((correlationData, ack, cause) ->{
            if(ack){
                System.out.println("消息发送到交换机成功...");
            }else{
                System.out.println("消息发送到交换机失败...");
            }
        });

        //设置消息发送到队列的回调
        /**
         * 消息发到队列失败时才会回调该方法
         * @param message:封装消息内容的对象
         * @param replyCode:错误码
         * @param replyText:错误对象
         * @param exchange:交换机
         * @param routingKey:路由键
         */
        template.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            if(retry_count > 0){
                System.out.println("再次发送消息...");
                template.convertAndSend(exchange, routingKey, message);
                retry_count--;
            }
        });
        return template;
    }
}

死信队列

死信,在官网中对应的单词为“Dead Letter”,可以看出翻译确实非常的简单粗暴。那么死信是个什么东西呢?

“死信”是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:

  1. 消息被否定确认,使用 channel.basicNackchannel.basicReject ,并且此时requeue 属性被设置为false
  2. 消息在队列的存活时间超过设置的TTL时间。
  3. 消息队列的消息数量已经超过最大队列长度。

那么该消息将成为“死信”。

“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。

代码思路(模拟消息过期场景):
1. 用户下单后,发送消息(带有TTL过期时间)到一个普通队列,然后这个普通队列设置了死信交换机路由
2. 到达过期时间后,消息成为死信,由死信交换机转发到死信队列
3. 消费者监听死信队列,处理过期订单
SpringBoot整合RabbitMQ (回调函数、死信队列)_第1张图片

修改RabbitmqConfig配置类

package cn.zsh.config.rabbit;

import cn.zsh.constants.BaseConstants;
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;

/**
 * RabbitMQ相关配置类
 */
@Configuration
public class RabbitmqConfig {

    public static final String EXCHANGE_TOPIC_0907 = "exchange_topic_0907";//交换机名称
//    public static final String QUEUE_NAME_SMS = "queue_sms";//短信队列名称
//    public static final String QUEUE_NAME_EMAIL = "queue_email";//邮件队列名称
//    public static final String QUEUE_NAME_STATION = "queue_station";//站内信队列名称
    public static final String ROUTINGKEY_QUEUE_SMS = "#.sms";//短信队列的routingkey
    public static final String ROUTINGKEY_QUEUE_EMAIL = "#.email";//邮件队列的routingkey
    public static final String ROUTINGKEY_QUEUE_STATION = "#.station";//站内信队列的routingkey


    public static final String EXCHANGE_DEAD = "exchange_dead";//死信交换机名称
    public static final String ROUTINGKEY_QUEUE_DEAD = "routing_dead_key";//死信的路由key
    public static final String QUEUE_NAME_ORDER = "queue_dead";//死信队列名称



    //注册交换机
    @Bean
    public Exchange topicExchange(){
        //durable:设置持久化
//        return  new TopicExchange(EXCHANGE_TOPIC_0907,true,false);
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_0907).durable(true).build();
    }

    //注册短信队列-用来发短信
    @Bean
    public Queue smsQueue(){

        return new Queue(BaseConstants.RabbitMQConstants.QUEUE_NAME_SMS, true, false, false);
    }

    //注册邮件队列-用来发邮件
    @Bean
    public Queue emailQueue(){
        return new Queue(BaseConstants.RabbitMQConstants.QUEUE_NAME_EMAIL, true, false, false);
    }

    //注册站内信队列-用来发邮件
    @Bean
    public Queue stationQueue(){
        Map<String, Object> arguments = new HashMap<>(2);
        // 设置死信参数
        // 绑定死信交换机
        arguments.put("x-dead-letter-exchange", EXCHANGE_DEAD);
        // 绑定死信的路由key
        arguments.put("x-dead-letter-routing-key", ROUTINGKEY_QUEUE_DEAD);
        return new Queue(
                BaseConstants.RabbitMQConstants.QUEUE_NAME_STATION,
                true,
                false,
                false,
                arguments);
    }

    //短信队列绑定到交换机
    @Bean
    public Binding bindSmsQueue(){
        return BindingBuilder.bind(smsQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_SMS).noargs();
    }

    //邮件队列绑定到交换机
    @Bean
    public Binding bindEmailQueue(){
        return BindingBuilder.bind(emailQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_EMAIL).noargs();
    }

    //站内信队列绑定到交换机
    @Bean
    public Binding bindStationQueue(){
        return BindingBuilder.bind(stationQueue()).to(topicExchange()).with(ROUTINGKEY_QUEUE_STATION).noargs();
    }


    //注册死信交换机
    @Bean
    public Exchange deadExchange(){
        //durable:设置持久化
//        return  new TopicExchange(EXCHANGE_TOPIC_0907,true,false);
        return ExchangeBuilder.topicExchange(EXCHANGE_DEAD).durable(true).build();
    }
    //注册真实存储死信的队列,当死信队列中消息过期后,转发到此队列,真实存储死信队列需要绑定死信交换机和路由
    @Bean
    public Queue deadQueue(){
        return new Queue(QUEUE_NAME_ORDER, true, false, false);
    }

    //死信队列绑定到死信交换机
    @Bean
    public Binding bindDeadQueue(){
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(ROUTINGKEY_QUEUE_DEAD).noargs();
    }
}

生产者发送消息

rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPIC_0907, "course.email",JSONObject.toJSONString(emailDto)
        ,new MessagePostProcessor() {
                    //在消息发送之前应用于消息的处理器
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        MessageProperties messageProperties = message.getMessageProperties();
                        //设置过期时间TTL 5秒
                        messageProperties.setExpiration(String.valueOf(5000));
                        //设置持久化
                        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        return message;
                    }
                });

监听死信队列

@Component
@Slf4j
public class DeadConsumer {
    /**
     * 监听死信队列
     */
    @RabbitListener(queues = {BaseConstants.RabbitMQConstants.QUEUE_NAME_DEAD})
    public void handleEmail(String content, Message message, Channel channel){
        System.out.println("监听死信队列,收到的消息: "+ content);

        //.......

        //由于配置类做了手动ack  所以要手动签收消息
        try {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("发送短信,签收失败:" + e);
        }
    }
}

你可能感兴趣的:(rabbitmq,spring,boot,java)