1.MQ概述
1.1.初识MQ
分布式架构中微服务之间的通信主要有两种方式、同步和异步。Feign就是基于Http协议的同步通信方式,而MQ则是微服务异步调用的常用方案
对比Feign和MQ:
feign采用同步调用方式,具有时效性强等优点,但是性能低、吞吐量下降、耦合度高容易导致级联失败等缺点
MQ采用异步调用方式,具有性能高、吞吐量高、解耦合、流量削峰、故障隔离等优点;但是也存在架构复杂、业务没有明显流程线、跟踪管理困难、强烈依靠Broker的可靠性等缺点
MQ使用事件驱动模式作为异步调用的常用实现:下面就是MQ调用流程图
1.2.MQ常用技术方案
方案 | RabbitMQ | ActiveMQ | RocketMQ | kafka |
---|---|---|---|---|
公司 | Rabbit | Apache | 阿里巴巴 | Apache |
开发语言 | Erlang | java | java | scala & java |
可用性 | 高 | 一般 | 高 | 高 |
单机吞吐量 | 一般 | 差 | 高 | 极高 |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 毫秒级 |
消息可靠性 | 高 | 一般 | 高 | 一般 |
2.RabbitMQ
2.1.RabbitMQ通信结构
2.2.简单消息发送demo
2.2.1.引入AMQP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.2.2.配置rabbitmq
server:
port: 7713
spring:
rabbitmq:
host: localhost # 服务器地址
port: 5672 # 服务器端口
# virtual-host: / # 虚拟主机地址,每一个虚拟主机相当于一台独立的rabbitmq服务器,可以用来做环境隔离、业务拆分
username: guest # 服务器用户名
password: guest # 服务器密码
2.2.3.Producer消息生产者
package com.acx;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MqProducerTest {
@Test
public void pushMsg() {
try {
String queueName = "hello.word";
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//1.创建rabbitmq连接
Connection connection = connectionFactory.newConnection();
//2.创建channel通道
Channel channel = connection.createChannel();
//3.channel绑定队列
channel.queueDeclare(queueName, false, false, false, null);
//4.发送消息
String msg = "hello word!!!";
channel.basicPublish("", queueName, null, msg.getBytes());
System.out.println("publisher: mq消息发送成功!!!! msg=" + msg);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
2.2.4.Consumer消息消费者
package com.acx;
import com.rabbitmq.client.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MqConsumerTest {
@Test
public void consumerMsg() {
try {
String queueName = "hello.word";
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//1.创建rabbitmq连接
Connection connection = connectionFactory.newConnection();
//2.创建channel通道并绑定队列
Channel channel = connection.createChannel();
//3.channel绑定队列
channel.queueDeclare(queueName, false, false, false, null);
//4.接收消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body);
System.out.println("消费了队列 ---- queue=" + queueName + " msg=" + msg);
}
});
System.out.println("begin consumer mq msg");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
3.Spring AMQP
3.1.概述
AMQP:是用于在应用程序之间传递业务消息的开放标准。此协议与语言和平台无关,更符合微服务中独立性的要求。
Spring AMQP:是基于AMQP协议定义的一套API规范,提供了模板来发送和接受消息。主要包括两部分,其中Spring-AMQP是基础抽象,Spring-Rabbit是底层默认实现。
3.2.消息简单发送
3.2.1.配置queue
package com.acx.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMqConfig {
@Bean
public Queue helloWordQueue() {
return new Queue("helloWord");
}
}
3.2.生产者发送消息
package com.acx.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/mq_test")
public class MqController {
private static final Logger logger = LoggerFactory.getLogger(MqController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send_hello_word")
public String sendHelloWord() {
String msg = "hello word!!!";
//使用RabbitTemplate发送mq消息
rabbitTemplate.convertAndSend("helloWord", msg);
logger.info("rabbit mq发送端发送消息OK. msg={}", msg);
return msg;
}
}
3.2.3.消费者消费消息
package com.acx.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MqConsumer {
private static final Logger logger = LoggerFactory.getLogger(MqConsumer.class);
@RabbitListener(queues = "helloWord")
public void helloWordConsume(Message message) {
String msg = new String(message.getBody());
logger.info("rabbit mq 消费端获取消息OK. msg={}", msg);
}
}
3.3.工作队列_WorkQueue
3.3.1.生产者发送50条消息
package com.acx.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/mq_test")
public class MqController {
private static final Logger logger = LoggerFactory.getLogger(MqController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send_hello_word")
public String sendHelloWord() {
String msg = "hello word!!! index = ";
for (int i = 1; i <= 50; i++) {
rabbitTemplate.convertAndSend("helloWord", msg + i);
}
logger.info("rabbit mq发送端发送消息OK. msg={}", msg);
return msg;
}
}
3.3.2.消费者消费_两个消费端消费同一个队列
package com.acx.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MqConsumer {
private static final Logger logger = LoggerFactory.getLogger(MqConsumer.class);
@RabbitListener(queues = "helloWord")
public void helloWordConsume1(Message message) throws InterruptedException {
String msg = new String(message.getBody());
logger.info("rabbit mq 01 消费端获取消息OK. msg={}", msg);
//设置等待时间为20ms,这样这个消费端每秒可处理的就是50条消息
Thread.sleep(20);
}
@RabbitListener(queues = "helloWord")
public void helloWordConsume2(Message message) throws InterruptedException {
String msg = new String(message.getBody());
logger.error("rabbit mq 02 消费端获取消息OK. msg={}", msg);
//设置等待时间为100ms,这样这个消费端每秒可处理的就是10条消息
Thread.sleep(100);
}
}
3.3.3.测试结果
- 消费端1:拿到了25条消息,并且拿到的都是奇数索引的消息,一秒内全部消费完
- 消费端2:拿到了25条消息,并且拿到的都是偶数索引的消息 ,用时了3秒才消费完
2022-05-23 21:58:46.516 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 2
2022-05-23 21:58:46.516 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 1
2022-05-23 21:58:46.546 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 3
2022-05-23 21:58:46.578 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 5
2022-05-23 21:58:46.609 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 7
2022-05-23 21:58:46.624 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 4
2022-05-23 21:58:46.641 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 9
2022-05-23 21:58:46.672 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 11
2022-05-23 21:58:46.704 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 13
2022-05-23 21:58:46.736 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 15
2022-05-23 21:58:46.736 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 6
2022-05-23 21:58:46.768 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 17
2022-05-23 21:58:46.799 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 19
2022-05-23 21:58:46.830 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 21
2022-05-23 21:58:46.846 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 8
2022-05-23 21:58:46.862 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 23
2022-05-23 21:58:46.893 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 25
2022-05-23 21:58:46.924 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 27
2022-05-23 21:58:46.955 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 10
2022-05-23 21:58:46.955 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 29
2022-05-23 21:58:46.987 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 31
2022-05-23 21:58:47.018 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 33
2022-05-23 21:58:47.049 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 35
2022-05-23 21:58:47.065 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 12
2022-05-23 21:58:47.081 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 37
2022-05-23 21:58:47.112 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 39
2022-05-23 21:58:47.144 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 41
2022-05-23 21:58:47.175 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 14
2022-05-23 21:58:47.175 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 43
2022-05-23 21:58:47.206 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 45
2022-05-23 21:58:47.238 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 47
2022-05-23 21:58:47.268 INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 49
2022-05-23 21:58:47.283 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 16
2022-05-23 21:58:47.392 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 18
2022-05-23 21:58:47.500 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 20
2022-05-23 21:58:47.609 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 22
2022-05-23 21:58:47.719 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 24
2022-05-23 21:58:47.828 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 26
2022-05-23 21:58:47.939 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 28
2022-05-23 21:58:48.049 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 30
2022-05-23 21:58:48.159 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 32
2022-05-23 21:58:48.270 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 34
2022-05-23 21:58:48.377 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 36
2022-05-23 21:58:48.488 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 38
2022-05-23 21:58:48.596 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 40
2022-05-23 21:58:48.704 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 42
2022-05-23 21:58:48.814 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 44
2022-05-23 21:58:48.921 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 46
2022-05-23 21:58:49.031 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 48
2022-05-23 21:58:49.141 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 50
3.3.4.消费预取机制
预取机制:上述发送给队列的50条消息,因为RabbitMq默认的预取机制是无限条数,所以RabbitMQ的预取机制都会提前轮询规则平均将消息分配给每个消费端,这样即使消费端1的消费能力更强,但是两个消费端最终消费的消息数量是一样的。但是消费端2用了3秒才消费完消息,这显然是导致了消费端2的消费阻塞。
3.3.5.调整消费预取机制
- 将其预取消息调整为1,这样消费端只有处理完一条消息才会去拿下一条消息,而不是无限制条数预取。
# 消费端配置
server:
port: 7712
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
listener:
simple:
prefetch: 1 # 消息预取消息条数为1
4.SpringAMQP发布订阅模型
4.1.发布订阅模型
定义:发布订阅模式允许将同一消息发送给多个队列,通过exchange实现。
exchange类型:
-
Fanout广播: 将收到的消息路由到每一个跟其绑定的queue
-
Direct路由:会将消息根据routing规则路由到指定的queue,因为被称为路由模式
-
Topic主题:会将消息根据Topic规则路由到指定的queue,Topic和routing的却别就是Topic必须是多个单词的组合并且以 . 分割开来,如school.hotel.family
exchange作用:负责消息路由到队列而不是消息存储,路由失败则消息丢失
4.2.FanoutExchange广播
4.2.1.配置交换机和队列绑定
package com.acx.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutMqConfig {
@Bean
public Queue fanoutQueue1() {
return new Queue("fanoutQueue1");
}
@Bean
public Queue fanoutQueue2() {
return new Queue("fanoutQueue2");
}
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
@Bean
public Binding bindingFanoutQueue1(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue1") Queue queue) {
return BindingBuilder.bind(queue).to(exchange);
}
@Bean
public Binding bindingFanoutQueue2(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue2") Queue queue) {
return BindingBuilder.bind(queue).to(exchange);
}
}
4.2.2.生产者发送消息
package com.acx.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/mq_test")
public class MqController {
private static final Logger logger = LoggerFactory.getLogger(MqController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send_fanout_msg")
public String sendFanoutMsg() {
String msg = "广播exchange发送消息";
rabbitTemplate.convertAndSend("fanoutExchange", "", msg);
logger.info("rabbit mq发送广播消息成功. msg={}",msg);
return msg;
}
}
4.2.3.消费者消费消息
package com.acx.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class FanoutMqConsumer {
private static final Logger logger = LoggerFactory.getLogger(FanoutMqConsumer.class);
@RabbitListener(queues = "fanoutQueue1")
public void consumerFanoutQueue1(Message message) {
String msg = new String(message.getBody());
logger.info("消费端消费fanout_queue01队列消息成功. msg={}", msg);
}
@RabbitListener(queues = "fanoutQueue2")
public void consumerFanoutQueue2(Message message) {
String msg = new String(message.getBody());
logger.info("消费端消费fanout_queue02队列消息成功. msg={}", msg);
}
}
4.2.4.测试结果
- 我们可以看到两个队列都有这个消息并被相应的消费端消费成功了
2022-05-24 21:21:11.729 INFO 3848 --- [ntContainer#0-1] com.acx.consumer.FanoutMqConsumer : 消费端消费fanout_queue02队列消息成功. msg=广播exchange发送消息
2022-05-24 21:21:11.729 INFO 3848 --- [ntContainer#1-1] com.acx.consumer.FanoutMqConsumer : 消费端消费fanout_queue01队列消息成功. msg=广播exchange发送消息
4.3.DirectExchange路由交换机
4.3.1.配置交换机和队列绑定
package com.acx.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectMqConfig {
@Bean
public Queue directQueue1(){
return new Queue("directQueue1");
}
@Bean
public Queue directQueue2(){
return new Queue("directQueue2");
}
@Bean
public DirectExchange directExchange(){
return new DirectExchange("directExchange");
}
@Bean
public Binding bindingDirect1(@Qualifier("directExchange") DirectExchange exchange, @Qualifier("directQueue1") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("red");
}
@Bean
public Binding bindingDirect2(@Qualifier("directExchange") DirectExchange exchange,@Qualifier("directQueue1") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("blue");
}
@Bean
public Binding bindingDirect3(@Qualifier("directExchange") DirectExchange exchange,@Qualifier("directQueue2") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("yellow");
}
@Bean
public Binding bindingDirect4(@Qualifier("directExchange") DirectExchange exchange,@Qualifier("directQueue2") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("blue");
}
}
4.3.2.生产者生产消息
- 如下:我们发送了三次消息,分别使用了不同的routing策略
package com.acx.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/mq_test")
public class MqController {
private static final Logger logger = LoggerFactory.getLogger(MqController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send_direct_msg")
public String sendDirectMsg() {
String[] routingNames = new String[]{"red", "blue", "yellow"};
for (String routName : routingNames) {
String msg = "发送路由交换机消息。name=" + routName;
rabbitTemplate.convertAndSend("directExchange", routName, msg);
}
return "发送路由消息成功";
}
}
4.3.3.消费者消费消息
package com.acx.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DirectMqConsumer {
private static final Logger logger = LoggerFactory.getLogger(DirectMqConsumer.class);
@RabbitListener(queues = "#{directQueue1}")
public void consumerDirectQueue01(Message message) {
String msg = new String(message.getBody());
logger.info("路由交换机消费端。消费directQueue01队列消息成功. msg={}", msg);
logger.info("======================================================");
}
@RabbitListener(queues = "#{directQueue2}")
public void consumerDirectQueue02(Message message) {
String msg = new String(message.getBody());
logger.info("路由交换机消费端。消费directQueue02队列消息成功. msg={}", msg);
logger.info("======================================================");
}
}
4.3.4.测试结果
- 当按照blue路由规则发送消息时,两个队列都会接收到这个消息。而Red和Yellow则分别被队列1和队列2拿到并被消费端消费了。
2022-05-24 22:20:25.119 INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer : 路由交换机消费端。消费directQueue01队列消息成功. msg=发送路由交换机消息。name=red
2022-05-24 22:20:25.119 INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer : 路由交换机消费端。消费directQueue02队列消息成功. msg=发送路由交换机消息。name=blue
2022-05-24 22:20:25.119 INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer : ======================================================
2022-05-24 22:20:25.119 INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer : ======================================================
2022-05-24 22:20:25.120 INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer : 路由交换机消费端。消费directQueue01队列消息成功. msg=发送路由交换机消息。name=blue
2022-05-24 22:20:25.120 INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer : 路由交换机消费端。消费directQueue02队列消息成功. msg=发送路由交换机消息。name=yellow
2022-05-24 22:20:25.120 INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer : ======================================================
2022-05-24 22:20:25.120 INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer : ======================================================
4.4.TopicExchange主题交换机
4.4.1.配置交换机和队列绑定
- #:通配符,代指0个或者多个单词
- *:代指1个单词
package com.acx.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicMqConfig {
@Bean
public Queue topicQueue1(){
return new Queue("topicQueue1");
}
@Bean
public Queue topicQueue2(){
return new Queue("topicQueue2");
}
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topicExchange");
}
@Bean
public Binding bindingTopic1(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue1") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("china.#");
}
@Bean
public Binding bindingTopic2(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue1") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("#.news");
}
@Bean
public Binding bindingTopic3(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue2") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("japan.#");
}
@Bean
public Binding bindingTopic4(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue2") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("japan.news");
}
}
4.4.2.生产者生产消息
package com.acx.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/mq_test")
public class MqController {
private static final Logger logger = LoggerFactory.getLogger(MqController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send_topic_msg")
public String sendTopicMsg(){
String[] topicNames = new String[]{"chain.news","japan.weather","japan.news"};
for (String topicName : topicNames) {
String msg = "发送主题交换机消息。name=" + topicName;
rabbitTemplate.convertAndSend("topicExchange", topicName, msg);
}
return "发送主题消息成功";
}
}
4.4.3.消费者消费消息
package com.acx.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class TopicMqConsumer {
private static final Logger logger = LoggerFactory.getLogger(TopicMqConsumer.class);
@RabbitListener(queues = "topicQueue1")
public void consumerTopicQueue1(Message message) {
String msg = new String(message.getBody());
logger.info("消费topicQueue01消息成功. msg={}", msg);
logger.info("======================");
}
@RabbitListener(queues = "topicQueue2")
public void consumerTopicQueue2(Message message) {
String msg = new String(message.getBody());
logger.info("消费topicQueue02消息成功. msg={}", msg);
logger.info("======================");
}
}
4.4.4.测试结果
- 每次发送消息都会寻找符合Topic匹配规则的队列进行投递,然后消费端消费对应的队列消息
2022-05-24 22:54:49.860 INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer : 消费topicQueue02消息成功. msg=发送主题交换机消息。name=japan.weather
2022-05-24 22:54:49.860 INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer : ======================
2022-05-24 22:54:49.860 INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer : 消费topicQueue01消息成功. msg=发送主题交换机消息。name=chain.news
2022-05-24 22:54:49.860 INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer : ======================
2022-05-24 22:54:49.861 INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer : 消费topicQueue02消息成功. msg=发送主题交换机消息。name=japan.news
2022-05-24 22:54:49.861 INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer : ======================
2022-05-24 22:54:49.861 INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer : 消费topicQueue01消息成功. msg=发送主题交换机消息。name=japan.news
2022-05-24 22:54:49.861 INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer : ======================
4.5.注解方式进行队列关系绑定
-
以DirectExchange为例
-
使用注解以后就不需要config配置类进行配置绑定了,可以完全代替前面的DirectMqConfig配置类的作用
package com.acx.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DirectMqConsumer {
private static final Logger logger = LoggerFactory.getLogger(DirectMqConsumer.class);
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "directQueue1"),
exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void consumerDirectQueue01(Message message) {
String msg = new String(message.getBody());
logger.info("路由交换机消费端。消费directQueue01队列消息成功. msg={}", msg);
logger.info("======================================================");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "directQueue2"),
exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),
key = {"yellow", "blue"}
))
public void consumerDirectQueue02(Message message) {
String msg = new String(message.getBody());
logger.info("路由交换机消费端。消费directQueue02队列消息成功. msg={}", msg);
logger.info("======================================================");
}
}
5.消息转换器
5.1.概述
说明:ConverterAndSend发送的msg是object传参,也就是说我们可以发送任意对象类型的消息。SpringAMQP会帮我们将这个Object对象序列化为字节以后发送,默认是根据SimpleMessageConverter类实现的序列化转化,而SimpleMessageConverter实际集成的是MessageConverter类,MessageConverter类默认使用的是JDK自带的序列化工具类。
JDK序列化缺点:
- 无法跨语言:只支持Java语言其他语言不支持
- 序列化之后的码流太大
- 序列化性能太低
5.2.切换为jackson序列化工具类
5.2.1.发送端切换Jackson序列化工具类
package com.acx.config;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
}
5.2.2.消费端切换Jackson序列化工具类
package com.acx.config;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
}