RabbitMQ

在微服务互相调用时:

对时效性要求高,可以采用同步调用(比如OpenFeign)

对时效性要求不高,可以采取异步调用(比如MQ)

1 安装部署(Docker安装)

官网www.rabbitmq.com

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对象

7 消息可靠性

7.1 发送者可靠性

7.2 MQ可靠性

7.3 消费者可靠性

8 延迟消息

相关推荐
qqty12171 小时前
windows配置永久路由
java
weixin_404157681 小时前
Java高级面试与工程实践问题集(七)
java·开发语言·面试
计算机学姐1 小时前
基于SpringBoot+Vue的智能民宿预定游玩系统【AI智能客服+数据可视化】
java·vue.js·spring boot·后端·mysql·spring·信息可视化
wenlonglanying1 小时前
springcloud springboot nacos版本对应
spring boot·spring·spring cloud
骇客野人1 小时前
JDK8和JDK8以后对jdk的优化,以及为什么如此优化
java·开发语言·windows
要努力点1 小时前
26考研——计算机考研复试——0854(2)
java·c语言·考研·算法·复试
摇滚侠1 小时前
微信小程序是前端,也需要 Java 开发的后端服务
java·前端·微信小程序
lxf_gis1 小时前
【JavaEE】Spring Web MVC
前端·spring·java-ee
小手cool2 小时前
idea2025.3.3版本很卡
java·idea