RabbitMQ–集成Springboot–01–工作模式
代码位置
https://gitee.com/DanShenGuiZu/learnDemo/tree/master/rabbitMq-learn/rabbitMq-01
1、前置条件
1.1、新建一个虚拟分区
因为我需要MQ单独拿来测试使用,所以我新建一个分区
1.2、创建分区的用户
1.3、给用户授权
1.4、创建springboot
1.4.1、依赖
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1.4.2、配置
server:
port: 8080
spring:
#给项目来个名字
application:
name: rabbitmq-learn
#配置rabbitMq 服务器
rabbitmq:
host: 192.168.187.171
port: 5672
username: root
password: root
#虚拟host 可以不设置,使用server默认host
virtual-host: test
2、模式–简单模式
- 消息发送规则
- 生产者 发送消息 给交换机
- 交换机 通过 路由键 转发 给队列
- 简单模式 使用默认交换机,所以发送消息的时候,不需要配置交换机
- 简单模式 路由键的名称就是队列的名称,所以发送消息的时候,只要配置队列名称就行。
2.1、代码
@Configuration
public class Simple_Config {
static String queue_name = "code_simple_queue1";
// 声明队列
// @param1 队列名称
// @param2 是否持久化 持久化:RabbitMQ服务器重启,队列还存在;反之,不存在。
// 持久化的队列中的消息会存盘,不会随着服务器的重启会消失
// @param3 排他性 是否独占一个队列(一般不会)
// @param4 是否自动删除 随着最后一个消费者消费消息完毕后,是否自动删除这个队列
// @param5 携带一些附加信息 供其它消费者获取
@Bean
public Queue simpleQueue() {
return new Queue(queue_name, true, false, false, null);
}
}
@Component
public class Simple_Consumer {
// MQ中code_simple_queue1队列必须存在,否则就会报错
// 默认队列是持久化,非独占式的
@RabbitListener(queues = { "code_simple_queue1" })
public void receive(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("Simple_Consumer:" + new String(message.getBody()));
}
}
@RestController
@RequestMapping("/simple")
public class Simple_Producer {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsg")
public String sendMsgA() {
for (int i = 0; i < 5; i++) {
String msg = "simple_queue " + i;
//发送消息
rabbitTemplate.convertAndSend(null, "code_simple_queue1", msg);
}
return "ok";
}
}
2.2、代码结构和测试
http://127.0.0.1:8080/simple/sendMsg
3、模式–工作队列
- 工作队列模式 也叫 竞争消费者模式
- 多个消费端消费同一个队列中的消息
- 队列采用轮询的方式将消息是平均发送给消费者,此处的资源是竞争关系
- 与简单模式相比 多了一个(或者多个)消费端
- 它解决了当消息队列的消息过多的情况,单消费者消费速率有限,导致的消息堆积的问题。
- 工作队列模式中的队列是和默认的交换机 AMQP default 进行绑定(和简单模式一样)
- 保证同一个消息只能被一个消费者消费掉
- 可以设置一个开关,syncronize,保证一条消息只能被一个消费者使用
- 用于场景
- 红包场景
- 大型项目中的资源调度
- 有2这种模式
- 轮询分发
- 消息平均分配。不管谁忙,都不会多给消息,总是你一个我一个
- 公平分发
- 能者多劳。谁消费得快,谁就消费得多。
- 必须关闭自动应答 ACK,改成手动应答,并且设置 channel.basicQos(1),表示消费者一次只处理一条消息
- 轮询分发
3.1、轮询分发
- 消息平均分配。
- 不管谁忙,都不会多给消息,总是你一个我一个
3.1.1、代码
@Configuration
public class Work_Config {
static String queue_name1 = "code_work_queue1";
// 声明队列
// @param1 队列名称
// @param2 是否持久化 持久化:RabbitMQ服务器重启,队列还存在;反之,不存在。
// 持久化的队列中的消息会存盘,不会随着服务器的重启会消失
// @param3 排他性 是否独占一个队列(一般不会)
// @param4 是否自动删除 随着最后一个消费者消费消息完毕后,是否自动删除这个队列
// @param5 携带一些附加信息 供其它消费者获取
@Bean
public Queue workQueue1() {
return new Queue(queue_name1, true, false, false, null);
}
}
/**
* 2个消费者
*/
@Component
public class Work_Consumer {
@RabbitListener(queues = { "code_work_queue1" })
public void receive(Message message, Channel channel) throws Exception {
// 模拟任务耗时 5s
TimeUnit.SECONDS.sleep(5);
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("Work_Consumer1:" + new String(message.getBody()));
}
@RabbitListener(queues = { "code_work_queue1" })
public void receive2(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("Work_Consumer2:" + new String(message.getBody()));
}
}
@RestController
@RequestMapping("/work")
public class Work_Producer {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsg")
public String sendMsg() {
for (int i = 0; i < 10; i++) {
String msg = "work_queue " + i;
rabbitTemplate.convertAndSend("", "code_work_queue1", msg);
}
return "ok";
}
}
3.1.2、代码结构和测试
http://127.0.0.1:8080/work/sendMsg
3.2、公平分发
- 能者多劳。谁消费得快,谁就消费得多,就上面而言,Consumer2应该消费最多
- 实现步骤
- 消费者 必须 关闭自动应答 ACK,改成手动应答
- 消费者 设置 channel.basicQos(1),表示消费者一次只处理一条消息
3.2.1、代码改动点
# 手动ACK
listener:
simple:
#手动应答
acknowledge-mode: manual
# 最小消费者数量
concurrency: 1
# 最多消费者数量
max-concurrency: 10
# 是否支持重试
retry:
enabled: true
# 每次只处理一个消息
prefetch: 1
//手动ACk
channel.basicAck(deliveryTag, false);
3.2.2、代码结构和测试
4、模式–发布/订阅模式(fanout)
- 交换机类型为 fanout
- 相对于Work queues模式多了一个交换机,此处的资源是共享的
- 会把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中,无视 BindingKey
- 一个生产者,多个消费者
- 每个消费者都绑定一个自己的队列
- 生产者没有将消息直接发送给队列,而是发送给交换机(Exchange)
- 每个队列都需要绑定到交换机上
- 生产者发送的消息,经过交换机到达队列,可实现一个消息被多个消费者消费
4.1、代码
@Configuration
public class Fanout_Config {
/**
* 创建2个队列 :fanout_queue_1 fanout_queue_2 将2个队列都绑定在交换机 fanout_exchange 上
* 因为是扇型交换机, 路由键无需配置,配置也不起作用
*/
// 声明队列
@Bean
public Queue fanoutQueue1() {
return new Queue("fanout_queue_1");
}
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout_queue_2");
}
// 声明交换机
@Bean
public FanoutExchange exchange() {
return new FanoutExchange("fanout_exchange");
}
// 声明交换机与队列之间的关系
@Bean
public Binding bindingFanoutQueue1(Queue fanoutQueue1, FanoutExchange exchange) {
return BindingBuilder.bind(fanoutQueue1).to(exchange);
}
@Bean
public Binding bindingFanoutQueue2(Queue fanoutQueue2, FanoutExchange exchange) {
return BindingBuilder.bind(fanoutQueue2).to(exchange);
}
}
@Component
public class Fanout_Consumer {
@RabbitListener(queues = { "fanout_queue_1" })
public void receive(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("Fanout_Consumer1:" + new String(message.getBody()));
}
@RabbitListener(queues = { "fanout_queue_2" })
public void receive2(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("Fanout_Consumer2:" + new String(message.getBody()));
}
}
@RestController
@RequestMapping("/fanout")
public class Fanout_Producer {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsg")
public String sendMsg() {
for (int i = 0; i < 5; i++) {
String msg = "fanout_queue " + i;
// 消息发给交换机,不需要路由键
rabbitTemplate.convertAndSend("fanout_exchange", "", msg);
}
return "ok";
}
}
4.2、代码结构和测试
http://127.0.0.1:8080/fanout/sendMsg
5、模式—路由模式(Direct)
- Direct 模式在 Fanout 模式之上做了一个路由键 RoutingKey,对发送给交换机 Exchange 的消息进行筛选
- 每个消费者监听自己的队列,并且设置带统配符的 routingkey
- 生产者将消息发给broker,由交换机根据 routingkey 来转发消息到指定的队列
- 路由模式 = 发布/订阅模式(fanout) + 指定 RoutingKey
5.1、代码
@Configuration
public class Direct_Config {
// 声明队列
// @param1 队列名称
// @param2 是否持久化 持久化:RabbitMQ服务器重启,队列还存在;反之,不存在。
// 持久化的队列中的消息会存盘,不会随着服务器的重启会消失
// @param3 排他性 是否独占一个队列(一般不会),true:只能被当前创建的连接使用,而且当连接关闭后队列即被删除
// @param4 是否自动删除 随着最后一个消费者消费消息完毕后,是否自动删除这个队列
// @param5 携带一些附加信息 供其它消费者获取
// 声明队列
@Bean
public Queue directQueue1() {
return new Queue("direct_queue1", true, false, false, null);
}
@Bean
public Queue directQueue2() {
return new Queue("direct_queue2");
}
@Bean
public Queue directQueue3() {
return new Queue("direct_queue3");
}
// 声明交换机
@Bean
public DirectExchange directExchange() {
return new DirectExchange("direct_exchange");
}
// 声明交换机与队列之间的关系
// 路由键为 warning 的消息,路由到direct_queue1
@Bean
public Binding bindingDirectQueue1(Queue directQueue1, DirectExchange exchange) {
return BindingBuilder.bind(directQueue1).to(exchange).with("warning");
}
// 路由键为 warning,info的消息,路由到direct_queue2
@Bean
public Binding bindingDirectQueue2_1(Queue directQueue2, DirectExchange exchange) {
return BindingBuilder.bind(directQueue2).to(exchange).with("info");
}
@Bean
public Binding bindingDirectQueue2_2(Queue directQueue2, DirectExchange exchange) {
return BindingBuilder.bind(directQueue2).to(exchange).with("warning");
}
// 路由键为debug,info的消息,路由到direct_queue3
@Bean
public Binding bindingDirectQueue3_1(Queue directQueue3, DirectExchange exchange) {
return BindingBuilder.bind(directQueue3).to(exchange).with("debug");
}
@Bean
public Binding bindingDirectQueue3_2(Queue directQueue3, DirectExchange exchange) {
return BindingBuilder.bind(directQueue3).to(exchange).with("info");
}
}
@Component
public class Direct_Consumer {
@RabbitListener(queues = { "direct_queue1" })
public void receive(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("direct_queue1队列消息:" + new String(message.getBody()));
}
@RabbitListener(queues = { "direct_queue2" })
public void receive2(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("direct_queue2队列消息:" + new String(message.getBody()));
}
@RabbitListener(queues = { "direct_queue3" })
public void receive3(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("direct_queue3队列消息:" + new String(message.getBody()));
}
}
@RestController
@RequestMapping("/direct")
public class Direct_Producer {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsgA")
public String sendMsgA() {
String msg = "direct_queue warning";
rabbitTemplate.convertAndSend("direct_exchange", "warning", msg);
return "ok";
}
@RequestMapping("/sendMsgB")
public String sendMsgB() {
String msg = "direct_queue info";
rabbitTemplate.convertAndSend("direct_exchange", "info", msg);
return "ok";
}
@RequestMapping("/sendMsgC")
public String sendMsgC() {
String msg = "direct_queue debug";
rabbitTemplate.convertAndSend("direct_exchange", "debug", msg);
return "ok";
}
}
5.2、代码结构和测试
http://127.0.0.1:8080/direct/sendMsgA
http://127.0.0.1:8080/direct/sendMsgB
http://127.0.0.1:8080/direct/sendMsgC
6、模式–主题模式(Topic)
- topic类型的交换机 实际上是 direct类型的交换机的一种
- 都将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中
- topic 在匹配规则上进行了扩展。使用了通配符
- 工作流程
- 消息生产者生产消息,把消息交给交换机 exchange
- 交换机 exchange 根据 key 的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费
- 和路由模式区别
- 绑定键 BindingKey 中带有模糊匹配
- 路由键 RoutingKey 由多个单词构成
6.1、Topic 模型
- 路由键为 com.rabbitmq.client 的消息,会同时路由到 Q1、Q2、Q3
- 路由键为 com.hidden.client 的消息,会路由到 Q2、Q3
- 路由键为 com.hidden.demo 的消息,会路由到 Q3
- 路由键为 java.util.concurrent 的消息,会被丢弃或者返回给生产者,因为,它没有匹配任何路由键
6.1、代码
@Configuration
public class Topic_Config {
// 声明队列
@Bean
public Queue topicQueue1() {
return new Queue("topic_queue1");
}
@Bean
public Queue topicQueue2() {
return new Queue("topic_queue2");
}
@Bean
public Queue topicQueue3() {
return new Queue("topic_queue3");
}
// 声明交换机
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("topic_exchange");
}
// 声明交换机与队列之间的关系
// *:匹配一个词
// #:匹配一个或多个词
@Bean
public Binding topicBinding1(Queue topicQueue1, TopicExchange exchange) {
return BindingBuilder.bind(topicQueue1).to(exchange).with("*.rabbitmq.*");
}
@Bean
public Binding topicBinding2(Queue topicQueue2, TopicExchange exchange) {
return BindingBuilder.bind(topicQueue2).to(exchange).with("*.*.client");
}
@Bean
public Binding topicBinding3(Queue topicQueue3, TopicExchange exchange) {
return BindingBuilder.bind(topicQueue3).to(exchange).with("com.#");
}
}
@Component
public class Topic_Consumer {
@RabbitListener(queues = { "topic_queue1" })
public void receive(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("topic_queue1队列消息:" + new String(message.getBody()));
}
@RabbitListener(queues = { "topic_queue2" })
public void receive2(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("topic_queue2队列消息:" + new String(message.getBody()));
}
@RabbitListener(queues = { "topic_queue3" })
public void receive3(Message message, Channel channel) throws Exception {
// 消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 手动ACk
channel.basicAck(deliveryTag, false);
System.out.println("topic_queue3队列消息:" + new String(message.getBody()));
}
}
@RestController
@RequestMapping("/topic")
public class Topic_Producer {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMsgA")
public String sendMsgA() {
String msg = "topic_queue (com.rabbitmq.client)";
rabbitTemplate.convertAndSend("topic_exchange", "com.rabbitmq.client", msg);
return "ok";
}
@RequestMapping("/sendMsgB")
public String sendMsgB() {
String msg = "topic_queue (com.hidden.client)";
rabbitTemplate.convertAndSend("topic_exchange", "com.hidden.client", msg);
return "ok";
}
@RequestMapping("/sendMsgC")
public String sendMsgC() {
String msg = "topic_queue (com.hidden.demo)";
rabbitTemplate.convertAndSend("topic_exchange", "com.hidden.demo", msg);
return "ok";
}
}
6.2、代码结构和测试
http://127.0.0.1:8080/topic/sendMsgA
http://127.0.0.1:8080/topic/sendMsgB
http://127.0.0.1:8080/topic/sendMsgC