Spring Boot 整合 RabbitMQ

首先,引入maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置RabbitMQ服务信息

spring:
  application:
    name: demo
  rabbitmq:
    host: 192.168.3.4
    port: 5672
    username: guest
    password: guest
    virtual-host: demo
    publisher-confirms: true

增加配置类,配置RabbitTemplate

package org.example.rabbitmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Configuration
public class RabbitMQConfig {

    /**
     * 消息发送工具
     *
     * rabbitmq保证一条消息只会被队列的一个消费者消费(一个队列可以有多个消费者)
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        // 消息发送到rabbitmq服务器回调
        template.setConfirmCallback((correlationData, ack, cause) -> {
            // 发送消息时,可以指定correlationData
            log.info("发送回调,data:{},ack:{},cause:{}", correlationData, ack, cause);
        });
        // 失败回调(发送到队列失败),发送回调与失败回调一起使用,可以确认消息是否已经成功发送到mq相应的队列
        template.setMandatory(true);
        template.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            log.info("失败回调,message:{},replyCode:{},replyText:{},exchange:{},routingKey:{}", message, replyCode, replyText, exchange, routingKey);
        });
        return template;
    }
    
}

创建生产者类

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String exchange, String routingKey, String message) {
        // 参数介绍:交换机名字,路由建,消息内容
        rabbitTemplate.convertAndSend(exchange, routingKey, message, new CorrelationData(message));
        log.info("消息发送成功:{},{},{}", exchange, routingKey, message);
    }

}

创建Dierct类型交换机、相关队列,RabbitMQConfig 类增加如下代码:

/**
 * direct类型交换机
 * 消息的routingKey与交换机的routingKey匹配上(完全一样),则转发消息给队列。
 *
 * 如果两个队列(A、B)绑定同一个交换机,使用相同的routingKey,是否都会接收到消息?
 * 会
 */
public static final String DIRECT_EXCHANGE = "DIRECT_EXCHANGE";
public static final String DIRECT_QUEUE_A = "DIRECT_QUEUE_A";
public static final String DIRECT_QUEUE_B = "DIRECT_QUEUE_B";
public static final String DIRECT_ROUTING_KEY = "DIRECT_ROUTING_KEY";

@Bean
public DirectExchange directExchange() {
    // direct交换机
    return new DirectExchange(DIRECT_EXCHANGE);
}

@Bean
public Queue directQueueA() {
    // 名字,是否持久化
    return new Queue(DIRECT_QUEUE_A, true);
}

@Bean
public Binding bindingDirectA() {
    // 绑定一个队列,to: 绑定到哪个交换机上面,with:绑定的路由建(routingKey)
    return BindingBuilder.bind(directQueueA()).to(directExchange()).with(DIRECT_ROUTING_KEY);
}

@Bean
public Queue directQueueB() {
    // 名字,是否持久化
    return new Queue(DIRECT_QUEUE_B, true);
}

@Bean
public Binding bindingDirectB() {
    // 绑定一个队列,to: 绑定到哪个交换机上面,with:绑定的路由建(routingKey)
    return BindingBuilder.bind(directQueueB()).to(directExchange()).with(DIRECT_ROUTING_KEY);
}

创建消费者类

package org.example.rabbitmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Consumer {

    /**
     * 交换机(DIRECT_EXCHANGE)收到一条消息(message,路由键:DIRECT_ROUTING_KEY)
     *
     * 队列(DIRECT_QUEUE_B)和队列(DIRECT_QUEUE_A)都绑定了交换机(DIRECT_EXCHANGE),同时使用了相同的路由键(DIRECT_ROUTING_KEY)
     * 所以交换机(DIRECT_EXCHANGE)会同时把消息(message)转发给两个队列
     *
     * 消费者(DirectA_1)、消费者(DirectA_2)消费同一个队列(DIRECT_QUEUE_A)
     * 所以队列(DIRECT_QUEUE_A)的消息(message)要么被消费者(DirectA_1)消费,要么被消费者(DirectA_2)消费
     *
     * 队列(DIRECT_QUEUE_B)只有一个消费者,所以消费者(DirectB)也会消费到消息(message)
     *
     * @param message
     * @throws Exception
     */
    @RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_A)
    public void consumerDirectA_1(String message) throws Exception {
        log.info("队列:{},1,成功消费消息:{}", RabbitMQConfig.DIRECT_QUEUE_A, message);
    }
    
    @RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_A)
    public void consumerDirectA_2(String message) throws Exception {
        log.info("队列:{},2,成功消费消息:{}", RabbitMQConfig.DIRECT_QUEUE_A, message);
    }

    @RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_B)
    public void consumerDirectB(String message) throws Exception {
        log.info("队列:{},成功消费消息:{}", RabbitMQConfig.DIRECT_QUEUE_B, message);
    }
}

创建RabbitMQRunner,用于项目启动后测试发送消息

package org.example.rabbitmq;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class RabbitMQRunner implements ApplicationRunner {

    @Autowired
    private Producer producer;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // direct 类型,发消息,交换机会转发给路由键一样的队列
        producer.send(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.DIRECT_ROUTING_KEY, "This is a direct message !");
        Thread.sleep(1000);

        // 测试消息 发送回调(没有对应的交换机)
        producer.send("nothing", RabbitMQConfig.DIRECT_ROUTING_KEY, "This is a message ! without exchange");
        Thread.sleep(1000);

        // 测试消息 失败回调(消息已经到达交换机,但没有找到路由键对应的队列)
        producer.send(RabbitMQConfig.DIRECT_EXCHANGE, "nothing", "This is a message ! without routingKey");
        Thread.sleep(1000);
	}
}

启动运行如下:
在这里插入图片描述在这里插入图片描述
创建Topic类型交换机、相关队列,RabbitMQConfig 类增加如下代码:

/**
 * topic类型交换机,通过*、#来匹配消息的routingKey,根据自己需要消费消息
 * [*]:匹配一个单词(单词间通过[.]分隔)
 * [#]:匹配任意数量单词
 */
public static final String TOPIC_EXCHANGE = "TOPIC_EXCHANGE";
public static final String TOPIC_LOG_QUEUE = "TOPIC_LOG_QUEUE";
public static final String TOPIC_lOG_INFO_QUEUE = "TOPIC_LOG_INFO_QUEUE";
public static final String TOPIC_ROUTING_KEY_ALL_LOG = "TOPIC_ROUTING_KEY.#";
public static final String TOPIC_ROUTING_KEY_INFO_LOG = "TOPIC_ROUTING_KEY.INFO.*";

@Bean
public TopicExchange topicExchange() {
    // topic交换机
    return new TopicExchange(TOPIC_EXCHANGE);
}

@Bean
public Queue topicLogQueue() {
    // 名字,是否持久化,接收所有日志消息队列
    return new Queue(TOPIC_LOG_QUEUE, true);
}

@Bean
public Binding bindingLogTopic() {
    // 绑定一个队列,to: 绑定到哪个交换机上面,with:绑定的路由建(routingKey)
    return BindingBuilder.bind(topicLogQueue()).to(topicExchange()).with(TOPIC_ROUTING_KEY_ALL_LOG);
}

@Bean
public Queue topicLogInfoQueue() {
    // 名字,是否持久化,接收info级别日志消息
    return new Queue(TOPIC_lOG_INFO_QUEUE, true);
}

@Bean
public Binding bindingLogInfoTopic() {
    // 绑定一个队列,to: 绑定到哪个交换机上面,with:绑定的路由建(routingKey)
    return BindingBuilder.bind(topicLogInfoQueue()).to(topicExchange()).with(TOPIC_ROUTING_KEY_INFO_LOG);
}

增加消费者,Consumer类增加如下代码:

   /**
     * 交换机(TOPIC_EXCHANGE)收到两条消息(m1,路由:TOPIC_ROUTING_KEY.ERROR.PAY)、(m2,路由:TOPIC_ROUTING_KEY.INFO.ORDER)
     * m1、m2都匹配路由(TOPIC_ROUTING_KEY.#),同时m2还匹配路由(TOPIC_ROUTING_KEY.INFO.*)
     * 所以队列(TOPIC_LOG_QUEUE,路由TOPIC_ROUTING_KEY.#)会收到m1、m2两条消息
     * 队列(TOPIC_LOG_INFO_QUEUE,路由:TOPIC_ROUTING_KEY.INFO.*)收到m2一条消息
     *
     * @param message
     * @throws Exception
     */
    @RabbitListener(queues = RabbitMQConfig.TOPIC_LOG_QUEUE)
    public void consumerTopicLog(String message) throws Exception {
        log.info("队列:{},成功消费消息:{}", RabbitMQConfig.TOPIC_LOG_QUEUE, message);
    }

    @RabbitListener(queues = RabbitMQConfig.TOPIC_lOG_INFO_QUEUE)
    public void consumerTopicLogInfo(String message) throws Exception {
        log.info("队列:{},成功消费消息:{}", RabbitMQConfig.TOPIC_lOG_INFO_QUEUE, message);
    }

测试发送消息,RabbitMQRunner类增加如下代码:

// topic 类型,发消息,交换机会把消息发送给路由键匹配上的队列
producer.send(RabbitMQConfig.TOPIC_EXCHANGE, "TOPIC_ROUTING_KEY.INFO.ORDER", "This is a topic message for log info order!");
Thread.sleep(1000);
producer.send(RabbitMQConfig.TOPIC_EXCHANGE, "TOPIC_ROUTING_KEY.ERROR.ORDER", "This is a topic message for log error order!");
Thread.sleep(1000);

启动运行如下:
在这里插入图片描述
创建Fanout类型交换机、相关队列,RabbitMQConfig 类增加如下代码:

/**
 * fanout类型交换机,没有路由规则,发消息不用指定routingKey
 * 会把消息发送给所有绑定此交换机的队列(广播模式/发布订阅模式)
 */
public static final String FANOUT_EXCHANGE = "FANOUT_EXCHANGE";
public static final String FANOUT_QUEUE_A = "FANOUT_QUEUE_A";
public static final String FANOUT_QUEUE_B = "FANOUT_QUEUE_B";

@Bean
public FanoutExchange fanoutExchange() {
    // fanout交换机
    return new FanoutExchange(FANOUT_EXCHANGE);
}

@Bean
public Queue fanoutQueueA() {
    // 名字,是否持久化
    return new Queue(FANOUT_QUEUE_A, true);
}

@Bean
public Binding bindingFanoutA() {
    // 绑定一个队列,to: 绑定到哪个交换机上面
    return BindingBuilder.bind(fanoutQueueA()).to(fanoutExchange());
}

@Bean
public Queue fanoutQueueB() {
    // 名字,是否持久化
    return new Queue(FANOUT_QUEUE_B, true);
}

@Bean
public Binding bindingFanoutB() {
    // 绑定一个队列,to: 绑定到哪个交换机上面
    return BindingBuilder.bind(fanoutQueueB()).to(fanoutExchange());
}

增加消费者,Consumer类增加如下代码:

/**
 * 交换机(FANOUT_EXCHANGE)会把消息(m1)转发给和它绑定的所有队列
 * 即队列(FANOUT_QUEUE_A)、队列(FANOUT_QUEUE_B)都会收到消息(m1)
 * 但队列(FANOUT_QUEUE_A)的消息(m1)要么被消费者(FanoutA_1)消费,要么被消费者(FanoutA_2)消费
 *
 * @param message
 * @throws Exception
 */
@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_A)
public void consumerFanoutA_1(String message) throws Exception {
    log.info("队列:{},1,成功消费消息:{}", RabbitMQConfig.FANOUT_QUEUE_A, message);
}

@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_A)
public void consumerFanoutA_2(String message) throws Exception {
    log.info("队列:{},2,成功消费消息:{}", RabbitMQConfig.FANOUT_QUEUE_A, message);
}

@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_B)
public void consumerFanoutB(String message) throws Exception {
    log.info("队列:{},成功消费消息:{}", RabbitMQConfig.FANOUT_QUEUE_B, message);
}

测试发送消息,RabbitMQRunner类增加如下代码:

// fanout 类型,发消息,交换机直接把消息转发给绑定的队列
producer.send(RabbitMQConfig.FANOUT_EXCHANGE, null, "This is a fanout message !");
Thread.sleep(1000);

启动后运行结果如下:
在这里插入图片描述
用死信队列来实现延时处理消息,RabbitMQConfig类增加相关代码如下:

/**
 * 死信交换机,创建队列的时候设置的附带交换机,当队列的消息发送失败后会交给私死信交换机处理
 * 1、消息被拒绝(basic.reject 或者 basic.nack),并且requeue为false(不重新放回队列)
 * 2、消息的过期时间到了
 * 3、队列长度超过限制了
 * 可以利用消息过期时间来实现延时队列
 * 消息先发到 timeout 队列,等待过期时间 30s 后,由死信交换机发给死信队列处理
 */
public static final long TIME_OUT_MILLS = 30 * 1000;
public static final String TIME_OUT_KEY_NAME = "x-message-ttl";
public static final String DEAD_EXCHANGE_KEY_NAME = "x-dead-letter-exchange";
public static final String DEAD_ROUTING_KEY_NAME = "x-dead-letter-routing-key";

public static final String TIME_OUT_QUEUE = "TIME_OUT_QUEUE";
public static final String TIME_OUT_EXCHANGE = "TIME_OUT_EXCHANGE";
public static final String TIME_OUT_ROUTING_KEY = "TIME_OUT_ROUTING_KEY";
public static final String DEAD_QUEUE = "DEAD_QUEUE";
public static final String DEAD_EXCHANGE = "DEAD_EXCHANGE";
public static final String DEAD_ROUTING_KEY = "DEAD_ROUTING_KEY";

@Bean
public Queue timeoutQueue() {
    Map<String,Object> map = new HashMap<>();
    // 设置消息的过期时间,单位毫秒
    map.put(TIME_OUT_KEY_NAME, TIME_OUT_MILLS);
    // 设置死信交换机
    map.put(DEAD_EXCHANGE_KEY_NAME, DEAD_EXCHANGE);
    // 指定死信交换机的路由
    map.put(DEAD_ROUTING_KEY_NAME, DEAD_ROUTING_KEY);
    // 名字,是否持久化,是否仅声明队列的连接可见(其它连接不可见,断开连接后队列自动删除),是否自动删除(所有消费者断开连接后自动删除)
    return new Queue(TIME_OUT_QUEUE, true, false, false, map);
}

@Bean
public DirectExchange timeoutExchange() {
    // direct交换机
    return new DirectExchange(TIME_OUT_EXCHANGE);
}

@Bean
public Binding bindingTimeout() {
    // 绑定一个队列,to: 绑定到哪个交换机上面,with:绑定的路由建(routingKey)
    return BindingBuilder.bind(timeoutQueue()).to(timeoutExchange()).with(TIME_OUT_ROUTING_KEY);
}

@Bean
public Queue deadQueue() {
    // 名字,是否持久化
    return new Queue(DEAD_QUEUE, true);
}

@Bean
public DirectExchange deadExchange() {
    // direct交换机
    return new DirectExchange(DEAD_EXCHANGE);
}

@Bean
public Binding bindingDead() {
    // 绑定一个队列,to: 绑定到哪个交换机上面,with:绑定的路由建(routingKey)
    return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTING_KEY);
}

增加消费者,Consumer类增加如下代码:

/**
 * 消费延时后的消息
 * @param message
 * @throws Exception
 */
@RabbitListener(queues = RabbitMQConfig.DEAD_QUEUE)
public void consumerDead(String message) throws Exception {
    log.info("队列:{},成功消费消息:{}", RabbitMQConfig.DEAD_QUEUE, message);
}

测试发送延时消息,RabbitMQRunner类增加如下代码:

 	// 发送延时消息(死信交换机实现)
    producer.send(RabbitMQConfig.TIME_OUT_EXCHANGE, RabbitMQConfig.TIME_OUT_ROUTING_KEY, "This is a dead for delay message !");
    Thread.sleep(1000);

启动后运行结果如下:

Demo~
https://github.com/191720653/demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值