首先,引入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);
启动后运行结果如下: