1. RabbitMQ组件架构图
核心组件:生产者、消费者、交换器、队列、绑定键、路由键、Broker
详细请参考:RabbitMQ 基本概念介绍
2. 项目快速搭建
(1) pom文件添加amqp依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>basic-rabbitmq-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>basic-rabbitmq-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>
(2) application.properties 增加rabbitmq配置,对应 RabbitProperties 配置类
server.port=1050
spring.application.name=basic-rabbitmq-demo
spring.rabbitmq.host=192.168.174.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
# 设置虚拟主机host
spring.rabbitmq.virtual-host=/
(3) BasicRabbitmqDemoApplication 启动类
@SpringBootApplication
public class BasicRabbitmqDemoApplication {
public static void main(String[] args) {
SpringApplication.run(BasicRabbitmqDemoApplication.class, args);
}
}
3. Direct交换器
direct类型的交换器会把消息路由到 binding key 与 routing key 完全匹配的队列中。
(1) RabbitConfigDemo01 创建交换器、队列、绑定三个bean,后续由RabbitAdmin#initialize 进行初始化。
@Configuration
public class RabbitConfigDemo01 {
/**
* 交换器名称
*/
public static final String EXCHANGE = "exchange_demo01";
/**
* 队列名称
*/
public static final String QUEUE = "queue_demo01";
/**
* 路由键名称
*/
public static final String ROUTING_KEY = "routing_key_demo01";
/**
* 交换器
*/
@Bean
public DirectExchange demo01Exchange() {
return new DirectExchange(EXCHANGE, true, false);
}
/**
* 队列
*/
@Bean
public Queue demo01Queue() {
return new Queue(QUEUE, true, false, false);
}
/**
* 交换器与队列绑定
*/
@Bean
public Binding demo01Binding() {
return BindingBuilder.bind(demo01Queue()).to(demo01Exchange()).with(ROUTING_KEY);
}
}
(2) DemoMessage 消息实体类
public class DemoMessage implements Serializable {
/**
* 主键
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
// 省略getter、setter、toString方法
}
(3) ProducerController 控制器
@RestController
public class ProducerController {
@Autowired
private ProducerService producerService;
@PostMapping("/demo01SyncSend")
public void demo01SyncSend(@RequestBody DemoMessage demoMessage) {
try {
producerService.demo01SyncSend(demoMessage);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
(4) ProducerServiceImpl 使用RabbitTemplate发送消息
@Service
public class ProducerServiceImpl implements ProducerService {
Logger logger = LoggerFactory.getLogger(ProducerServiceImpl.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void demo01SyncSend(DemoMessage demoMessage) {
logger.info("发送消息内容:{}", demoMessage);
rabbitTemplate.convertAndSend(RabbitConfigDemo01.EXCHANGE, RabbitConfigDemo01.ROUTING_KEY, demoMessage);
}
}
(5) Demo01Consumer 监听指定的队列,消费推送过来的消息。
@Component
@RabbitListener(queues = RabbitConfigDemo01.QUEUE)
public class Demo01Consumer {
private Logger logger = LoggerFactory.getLogger(Demo01Consumer.class);
@RabbitHandler
public void onMessage(DemoMessage demoMessage) {
logger.info("消费消息[onMessage][线程编号:{},消息内容:{}]",
Thread.currentThread().getId(), demoMessage);
}
}
启动项目,打印如下所示的日志表示项目启动成功!
2021-02-03 10:40:03.813 INFO 16852 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 1050 (http) with context path ''
# 与rabbitmq服务端建立连接
2021-02-03 10:40:03.815 INFO 16852 --- [ main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [192.168.174.129:5672]
# 本地对端端口是65455
2021-02-03 10:40:03.900 INFO 16852 --- [ main] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#119aa36:0/SimpleConnection@72a2312e [delegate=amqp://admin@192.168.174.129:5672/, localPort= 65455]
2021-02-03 10:40:04.028 INFO 16852 --- [ main] c.e.b.BasicRabbitmqDemoApplication : Started BasicRabbitmqDemoApplication in 4.614 seconds (JVM running for 6.51)
rabbitmq 管理界面可以看到建立的连接,如下图所示:
使用postman请求接口发送消息
IDEA Console打印相关日志
# 生产者发送消息
2021-02-03 10:45:11.683 INFO 16852 --- [nio-1050-exec-1] c.e.b.service.impl.ProducerServiceImpl : 发送消息内容:DemoMessage{id=1001, name='李四', age=25}
# 消费者消费消息
2021-02-03 10:45:11.706 INFO 16852 --- [ntContainer#0-1] c.e.b.consumer.Demo01Consumer : 消费消息[onMessage][线程编号:47,消息内容:DemoMessage{id=1001, name='李四', age=25}]
4. Fanout交换器
fanout类型的交换器会把消息路由到与该交换器绑定的所有队列上,与路由键无关
(1) RabbitConfigDemo02 新建1个交换器和2个队列的bean
@Configuration
public class RabbitConfigDemo02 {
public static final String EXCHANGE = "exchange_demo02";
public static final String QUEUE_A = "queue_demo02_A";
public static final String QUEUE_B = "queue_demo02_B";
/**
* 交换器
*/
@Bean
public FanoutExchange demo02Exchange() {
return new FanoutExchange(EXCHANGE, true, false);
}
/**
* 队列A
*/
@Bean
public Queue demo02QueueA() {
return new Queue(QUEUE_A, true, false, false);
}
/**
* 队列B
*/
@Bean
public Queue demo02QueueB() {
return new Queue(QUEUE_B, true, false, false);
}
/**
* 交换器绑定队列A
*/
@Bean
public Binding demo02BindingQueueA() {
return BindingBuilder.bind(demo02QueueA()).to(demo02Exchange());
}
/**
* 交换器绑定队列B
*/
@Bean
public Binding demo02BindingQueueB() {
return BindingBuilder.bind(demo02QueueB()).to(demo02Exchange());
}
}
(2) ProducerController 控制器增加接口方法
@PostMapping("/demo02SyncSend")
public void demo02SyncSend(@RequestBody DemoMessage demoMessage) {
try {
producerService.demo02SyncSend(demoMessage);
} catch (Exception ex) {
ex.printStackTrace();
}
}
(3) ProducerServiceImpl 增加方法
@Override
public void demo02SyncSend(DemoMessage demoMessage) {
logger.info("fanout交换器,发送消息内容:{}", demoMessage);
rabbitTemplate.convertAndSend(RabbitConfigDemo02.EXCHANGE, null, demoMessage);
}
(4) 新增两个消费者
@Component
@RabbitListener(queues = RabbitConfigDemo02.QUEUE_A)
public class Demo02ConsumerA {
private Logger logger = LoggerFactory.getLogger(Demo02ConsumerA.class);
@RabbitHandler
public void onMessage(DemoMessage demoMessage) {
logger.info("fanout交换器,消费者A [onMessage][线程编号:{},消息内容:{}]",
Thread.currentThread().getId(), demoMessage);
}
}
@Component
@RabbitListener(queues = RabbitConfigDemo02.QUEUE_B)
public class Demo02ConsumerB {
private Logger logger = LoggerFactory.getLogger(Demo02ConsumerB.class);
@RabbitHandler
public void onMessage(DemoMessage demoMessage) {
logger.info("fanout交换器,消费者B [onMessage][线程编号:{},消息内容:{}]",
Thread.currentThread().getId(), demoMessage);
}
}
重新启动项目,使用postman访问接口方法。
IDEA Console打印日志信息符合fanout交换器的特点,消息被两个消费者成功消费
# 生产者发送消息
2021-02-03 13:50:24.720 INFO 17324 --- [nio-1050-exec-2] c.e.b.service.impl.ProducerServiceImpl : fanout交换器,发送消息内容:DemoMessage{id=1003, name='fanout交换器消息...', age=25}
# 消费者A消费消息
2021-02-03 13:50:24.835 INFO 17324 --- [ntContainer#1-1] c.e.b.consumer.Demo02ConsumerA : fanout交换器,消费者A [onMessage][线程编号:52,消息内容:DemoMessage{id=1003, name='fanout交换器消息...', age=25}]
# 消费者B消费消息
2021-02-03 13:50:24.835 INFO 17324 --- [ntContainer#2-1] c.e.b.consumer.Demo02ConsumerB : fanout交换器,消费者B [onMessage][线程编号:54,消息内容:DemoMessage{id=1003, name='fanout交换器消息...', age=25}]
5. Topic 交换器
与direct交换器类似,binding key允许使用通配符与routing key进行匹配,"*" 用于匹配一个单词,"#" 用于匹配多个单词(可以是零个)。binding key 与 routing key 都是根据句点号 “.” 来分隔字符串的,每一段独立的字符串看作一个单词,例如www.baidu.com表示3个单词。
(1) RabbitConfigDemo03 初始化交换器、队列、绑定三个bean
@Configuration
public class RabbitConfigDemo03 {
/**
* 交换器名称
*/
public static final String EXCHANGE = "exchange_demo03";
/**
* 队列名称
*/
public static final String QUEUE = "queue_demo03";
/**
* 模糊匹配路由键
*/
public static final String ROUTING_KEY = "#.key.demo03";
/**
* 交换器
*/
@Bean
public TopicExchange demo03Exchange() {
return new TopicExchange(EXCHANGE, true, false);
}
/**
* 队列
*/
@Bean
public Queue demo03Queue() {
return new Queue(QUEUE, true, false, false);
}
/**
* 交换器与队列绑定
*/
@Bean
public Binding demo03Binding() {
return BindingBuilder.bind(demo03Queue()).to(demo03Exchange()).with(ROUTING_KEY);
}
}
(2) ProducerController 控制器添加接口方法
/**
* topic交换器
*/
@PostMapping("/demo03SyncSend")
public void demo03SyncSend(@RequestBody DemoMessage demoMessage) {
try {
producerService.demo03SyncSend(demoMessage);
} catch (Exception ex) {
ex.printStackTrace();
}
}
(3) ProducerServiceImpl 新增方法
@Override
public void demo03SyncSend(DemoMessage demoMessage) {
logger.info("topic交换器,发送消息内容:{}", demoMessage);
rabbitTemplate.convertAndSend(RabbitConfigDemo03.EXCHANGE, "test.summer.key.demo03", demoMessage);
}
(4) Demo03Consumer 新增消费者,监听指定队列。
@Component
@RabbitListener(queues = RabbitConfigDemo03.QUEUE)
public class Demo03Consumer {
private Logger logger = LoggerFactory.getLogger(Demo03Consumer.class);
@RabbitHandler
public void onMessage(DemoMessage demoMessage) {
logger.info("topic类型交换器 [onMessage][线程编号:{},消息内容:{}]",
Thread.currentThread().getId(), demoMessage);
}
}
重新启动项目,使用postman请求接口方法发送消息
IDEA控制台打印消费成功的日志,说明路由键 “test.summer.key.demo03”可以正常匹配绑定键 “#.key.demo03”。
# 生产者发送消息
2021-02-03 15:38:17.516 INFO 14148 --- [nio-1050-exec-2] c.e.b.service.impl.ProducerServiceImpl : topic交换器,发送消息内容:DemoMessage{id=1006, name='topic交换器消息...', age=25}
# 消费者消费消息
2021-02-03 15:38:17.643 INFO 14148 --- [ntContainer#3-1] c.e.b.consumer.Demo03Consumer : topic类型交换器 [onMessage][线程编号:52,消息内容:DemoMessage{id=1006, name='topic交换器消息...', age=25}]
6. 交换器参数说明
交换器的抽象类是AbstractExchange,下图的三个子类分别对应三种交换器类型。
交换器的源码及参数说明:
public abstract class AbstractExchange extends AbstractDeclarable implements Exchange {
private final String name;
private final boolean durable;
private final boolean autoDelete;
private boolean delayed;
private boolean internal;
// 省略n行代码
}
参数 | 作用 |
---|---|
name | 交换器的名称 |
durable | 是否持久化 |
autoDelete | 是否自动删除 |
internal | 是否内置交换器 |
(1) 自动删除
自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与它解绑,才会自动删除交换器。必须有解除绑定这个动作,并且是在全部解绑之后。
(2) 内置交换器
客户端无法直接发送消息到内置交换器,只能通过交换器路由到内置交换器。
7. 队列参数说明
队列对应Queue类,源码及参数说明如下:
public class Queue extends AbstractDeclarable implements Cloneable {
/**
* Argument key for the master locator.
* @since 2.1
*/
public static final String X_QUEUE_MASTER_LOCATOR = "x-queue-master-locator";
private final String name;
private final boolean durable;
private final boolean exclusive;
private final boolean autoDelete;
public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete,
@Nullable Map<String, Object> arguments) {
super(arguments);
Assert.notNull(name, "'name' cannot be null");
this.name = name;
this.actualName = StringUtils.hasText(name) ? name
: (Base64UrlNamingStrategy.DEFAULT.generateName() + "_awaiting_declaration");
this.durable = durable;
this.exclusive = exclusive;
this.autoDelete = autoDelete;
}
// 省略n行代码
}
参数 | 作用 |
---|---|
name | 队列的名字 |
durable | 是否持久化 |
exclusive | 是否排他 |
autoDelete | 是否自动删除 |
arguments | 一组其他参数 |
(1) 排他队列
排他队列值仅对首次声明它的连接可见,并在连接断开时自动删除。
(2) 自动删除
当连接该队列的消费者都断开之后,队列删除,不管队列中是否有数据。
(3) arguments 参数说明
参数 | 作用 |
---|---|
x-message-ttl | 消息过期时间,单位毫秒 |
x-expires | 设置多长时间队列未使用将被删除,单位毫秒 |
x-max-length | 队列消息的最大长度值 |
x-max-length-bytes | 队列占用的最大空间大小,单位字节 |
x-dead-letter-exchange | 设置死信交换器 |
x-dead-letter-routing-key | 设置死信交换器的路由键 |
x-queue-mode | lazy 懒加载,先将消息保存到磁盘上,消费的时候加载到内存 |
至此,Spring Boot整合消息中间件RabbitMQ入门分享完毕