在微服务互相调用时:
对时效性要求高,可以采用同步调用(比如OpenFeign)
对时效性要求不高,可以采取异步调用(比如MQ)
1 安装部署(Docker安装)
1.1 创建镜像
方式一:使用pull命令拉取
cs
docker pull rabbitmq:4.2.4-management
方式二:(有rabbitmq.tar包)使用load命令加载
cs
docker load -i rabbitmq4.2.4.tar
1.2 生成容器
使用docker-compose.yml文件生成容器
bash
service:
rabbitmq:
image: rabbitmq:4.2.4-management
container_name: rabbitmq_container
restart: always #自动重启
ports:
- "15672:15672" #web控制台
- "5672:5672" #收发消息的端口
environment:
RABBITMQ_DEFAULT_USER: rabbitmq #帐号
RABBITMQ_DEFAULT_PASS: 123
# volumes:
# - "/root/docker_rabbitmq/mq-plugins:/plugins" #插件目录
networks:
- sunner_network
1.3 控制台访问
输入服务器http://IP:15672访问控制台,输入账号密码进入
2 RabbitMQ控制台
virtual-host: 虚拟主机(起到数据隔离的作用)
publisher: 消息发送者
consumer: 消息接受者
queue: 队列(储存消息)
exchange: 交换机(没有储存消息功能,只负责路由消息)

2.1 新建用户
在创建rabbitMQ的docker容器时,会默认创建一个用户

新建得用户还没有任何可访问的虚拟机

2.2 给用户(创建/分配)虚拟机
退出登录,然后登陆刚刚创建的新用户,新建虚拟机:

查看新建的虚拟机,新建的虚拟机会包含一些默认的交换机Exchange

2.3 给虚拟机添加消息队列

3 JAVA客户端
3.1 SpringAMQP
SpringAMQP是基于"AMQP协议"(应用程序之间传递消息的开放标准)定义的一套API规范,提供了模板来接收和发送消息
3.1.1 引入spring-amqp依赖(publisher和consumer都可使用)
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.1.2 在微服务引入MQ服务端消息(配置application.yaml)
XML
spring:
rabbitmq:
host: 123.123.123.123 #主机名
port: 5672 #端口
virtual-host: /abc #虚拟主机
username: zhangsan #用户名
password: 666888999 #密码
listener:
simple:
prefetch: 1 #每次只能获取1条消息,处理完成后才能获取下一个消息,这样性能好的机器能处理更多消息,性能差的机器处理少量消息
3.2 RabbitTemplate
SpringAMQP提供了RabbitTemplate工具类,方便我们发送消息
3.2.1 发送消息(Publisher)
此案例中是直接向队列发送消息(不是向交换机)
java
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/rabbitmqOnePublisher.do")
public Result rabbitmqOnePublisher() {
// 队列名
String queueName = "engineering.one";
// 消息
String message = "Hello, spring amqp 66666";
// 发送消息
rabbitTemplate.convertAndSend(queueName, message);
return Result.success();
}
3.2.2 接收消息(Consumer)
SpringAMQP提供声明式的消息监听,只需要通过@RabbitListener注解在方法上声明要监听的队列名称,将来SpringAMQP就会把消息传递给当前方法
被监听的方法不能有返回值,即方法的返回值是void
java
@RabbitListener(queues = {"engineering.one", "engineering.two"})
public void rabbitmqOneConsumer(String message) {
System.out.println("接收消息 : " + message);
}
3.2.3 WorkQueue模型
如果多个消费者(同一个微服务部署在不同服务器上)绑定同一个队列,可以加快消息的处理速度,而且一个消息只能被消费一次
队列默认采用的是消息平均分配给消费者,但是每个服务器的处理速度不同,就会造成"盲目平均",可以配置prefetch: 1来控制消费者预取的消息数量,消费者处理完一条消息后才能再获取下一条,实现能者多劳
4 交换机Exchange
交换机的作用是接收发送者发送的消息,并将消息路由到与其绑定的队列
此时消息发送者就不再是发消息给队列,而是发消息给交换机
4.1 Fanout(扇出)交换机
Fanout Exchange会将接收到的消息路由到所有与其绑定的队列

4.1.1 操作RabbitMQ控制台
新建队列"engineering.fanout.queue1"和"engineering.fanout.queue2"
新建交换机"engineering.fanout"

交换机与队列绑定

4.1.2 操作JAVA客户端
发送消息
java
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/rabbitmqOnePublisher.do")
public Result rabbitmqOnePublisher() {
// 交换机名
String exchangeName = "engineering.fanout";
// 消息
String message = "Hello, everyone";
// 发送消息 参数:交换机名,路由规则,消息
rabbitTemplate.convertAndSend(exchangeName, "", message);
return Result.success();
}
接收消息
java
@RabbitListener(queues = "engineering.fanout.queue1")
public void rabbitmqOneConsumer1(String message) throws InterruptedException {
System.out.println("消费者11111111111111接收消息 : " + message);
}
@RabbitListener(queues = "engineering.fanout.queue2")
public void rabbitmqOneConsumer2(String message) throws InterruptedException {
System.out.println("消费者2接收消息 : " + message);
}
4.2 Direct(定向)交换机
Direct Exchange会将接收到的消息按规则路由到指定队列,因此称为定向路由
每个Queue都与Exchange设置一个BindingKey
消息发送者发消息时,指定消息的RoutingKey
Exchange会将消息路由到BindigKey和RoutingKey一致的队列,如果多个队列的BindingKey相同时,当消息发送者发的是此RoutingKey的消息时,此时Direct交换机与Fanout交换机的功能类似

4.2.1 操作RabbitMQ控制台
新建队列"engineering.direct.queue1"和"engineering.direct.queue2"
新建交换机"engineering.direct"
交换机与队列绑定,设置BindingKey

4.2.2 操作JAVA客户端
发送消息
java
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/rabbitmqOnePublisher.do")
public Result rabbitmqOnePublisher(String routingKey) {
// 交换机名
String exchangeName = "engineering.direct";
// 消息
String message = "此时是发送" + routingKey + "规则的消息";
// 发送消息 参数:交换机名,路由规则,消息
rabbitTemplate.convertAndSend(exchangeName, routingKey, message);
return Result.success();
}
接收消息
java
@RabbitListener(queues = "engineering.direct.queue1")
public void rabbitmqOneConsumer1(String message) throws InterruptedException {
System.out.println("消费者11111111111111接收消息 : " + message);
}
@RabbitListener(queues = "engineering.direct.queue2")
public void rabbitmqOneConsumer2(String message) throws InterruptedException {
System.out.println("消费者2接收消息 : " + message);
}
4.3 Topic(主题,话题)交换机
Topic Exchange也是基于RoutingKey做消息路由,但RoutingKey通常是多个单词的组合,并以**.**分割
Queue与Topic Exchange绑定时,也可以用多个单词绑定,并且BindingKey可以使用通配符
# 代表0个或多个单词
* 代表1个单词

4.3.1 操作RabbitMQ控制台
新建队列"engineering.topic.queue1"和"engineering.topic.queue2"
新建交换机"engineering.topic"
交换机与队列绑定,设置BindingKey

4.3.2 操作JAVA客户端
发送消息
java
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/rabbitmqOnePublisher.do")
public Result rabbitmqOnePublisher(String routingKey) {
// 交换机名
String exchangeName = "engineering.topic";
// 消息
String message = "此时是发送" + routingKey + "规则的消息";
// 发送消息 参数:交换机名,路由规则,消息
rabbitTemplate.convertAndSend(exchangeName, routingKey, message);
return Result.success();
}
接收消息
任何以"china....."开头的RoutingKey都会分配给"engineering.topic.queue1"队列
任何以"......news"结尾的RoutingKey都会分配给"engineering.topic.queue2"队列
任何以"china......new"开头和结尾的RoutingKey则会同时分配给"engineering.topic.queue1"和"engineering.topic.queue2"队列
java
@RabbitListener(queues = "engineering.topic.queue1")
public void rabbitmqOneConsumer1(String message) throws InterruptedException {
System.out.println("消费者11111111111111接收消息 : " + message);
}
@RabbitListener(queues = "engineering.topic.queue2")
public void rabbitmqOneConsumer2(String message) throws InterruptedException {
System.out.println("消费者2接收消息 : " + message);
}
5 声明队列,交换机,绑定关系
声明好(队列,交换机,绑定关系)后,只要程序一直保持运行
当删除 被**@RabbitListener监听** 的任意一个队列 (只有删除被监听的队列,不针对交换机,不针对绑定关系),此时会重新更新所有队列,所有交换机,所有绑定关系
5.1 基于@Bean声明队列和交换机(不推荐)
声明队列 (用new的方式; 或用工厂类QueueBuilder的方式)
声明交换机 (用new的方式; 或用工厂类ExchangeBuilder的方式)
声明队列和交换机的绑定关系
java
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.ExchangeBuilder;
@Configuration //"配置类"注解
public class EngineeringAutoConfiguration {
// 声明engineering.topic.queue1队列
@Bean
public Queue topicQueue1() {
// 方式一:使用new的方式
// return new Queue("engineering.topic.queue1");
// 方式二:使用工厂类QueueBuilder的方式
return QueueBuilder.durable("engineering.topic.queue1").build();
}
// 声明engineering.topic.queue1队列
@Bean
public Queue topicQueue2() {
// 方式一:使用new的方式
// return new Queue("engineering.topic.queue2");
// 方式二:使用工厂类QueueBuilder的方式
return QueueBuilder.durable("engineering.topic.queue2").build();
}
// 声明engineering.topic交换机
@Bean
public TopicExchange topicExchange() {
// 方式一:使用new的方式
// return new TopicExchange("engineering.topic");
// 方式二:使用工厂类ExchangeBuilder的方式
return ExchangeBuilder.topicExchange("engineering.topic").build();
}
// 绑定队列和交换机
@Bean
public Binding bindingTopic1(Queue topicQueue1, TopicExchange topicExchange) {
return BindingBuilder.bind(topicQueue1).to(topicExchange).with("china.#"); // 如果是FanoutExchange,此处不用写".with()"
}
// 绑定队列和交换机
@Bean
public Binding bindingTopic2(Queue topicQueue2, TopicExchange topicExchange) {
return BindingBuilder.bind(topicQueue2).to(topicExchange).with("#.news"); // 如果是FanoutExchange,此处不用写".with()"
}
}
5.2 基于注解声明队列和交换机(推荐)
SpringAMQP还提供了基于@RabbitListener注解来声明队列和交换机的方式
※ 此注解同时具备"创建队列,创建交换机,绑定队列和交换机"和 "监听队列"的作用
java
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(name = "engineering.topic.queue1"),
exchange = @Exchange(name = "engineering.topic", type = ExchangeTypes.TOPIC),
key = {"guardNet.#", "#.guardNet", "alWindow.#", "#.alWindow"}),
@QueueBinding(
value = @Queue(name = "engineering.direct.queue1"),
exchange = @Exchange(name = "engineering.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"})}
)
public void rabbitmqOneConsumer1(String message) {
System.out.println("消费者11111111111111接收消息 : " + message);
}
6 消息转换器
当使用rabbitTemplate.convertAndSend(String exchange, String routingKey, Object object)发送消息时,此时方法里面的第3个参数(即消息参数)是Object类型,因此Publisher可以发送任意类型的消息
6.1 使用默认的消息转换器(不推荐)的缺点
JDK序列化有安全风险
JDK序列化消息数据量大
JDK序列化消息的可读性差(在RabbitMQ控制台是base64码)
6.2 使用jackson(推荐)解决以上痛点
在Publisher和Consumer中都引入jackson的依赖
XML
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
在Publisher和Consumer配置MessageConvert
java
@Configuration //"配置类"注解
public class EngineeringAutoConfiguration {
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
6.3 使用fastjson2(推荐)解决以上痛点
在Publisher和Consumer中引入fastjson2的依赖
XML
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.61</version>
</dependency>
在Publisher微服务中使用rabbitTemplate.convertAndSend(exchangeName, routingKey, JSON.toJSONString(alWindowModels)),将消息转换为Sring类型再发送消息
在Consumer微服务中被@RabbitListener监听的方法使用String类型形参用于接收消息,再通过JSON.parseObject(fastjsonString, AlWindowModels.class)或JSON.parseArray(fastjsonString, AlWindowModels.class)将消息转换为对应的java对象