面试官问到你RabbitMQ你就这么告诉他

96 阅读7分钟

1. 消息中间件

  1. 中间件概念:

    中间件是一种独立的系统软件服务程序,位于客户机服务器的操作系统之上,管理着计算资源和网络通信。

    分布式应用系统可以借助中间件这种软件在不同的技术之间共享资源。

  2. 消息中间件:

    支持在分布式系统之间 发送和接收消息的软件。

2. 消息中间件使用场景

  1. 应用解耦

  2. 异步消息通信

  3. 流量削峰

3. AMQP协议(Advanced Message Queue Protocol)

  • AMQP协议概念

    概念描述
    Broker接收和分发消息的应用,RabbitMQ Server 就是 Message Broker。
    Virtual Host为了在一个单独的代理上实现多个隔离的环境(用户、用户组、交换机、队列 等),AMQP 提供了一个虚拟主机(virtual hosts)的概念,当多个不同的用户使用同一个RabbitMQ时,可以划分出多个vhost,每个用户在自己的vhost创建exchange、queue等。
    Connection生产者、消费者和Broker之间的TCP连接。
    Channel是在Connection内部建立的逻辑连接,它作为轻量级的Connection,极大减少了操作系统建立TCP连接的开销。
    Exchange交换机,用于接收消息,根据分发规则,匹配Routing Key,分发消息到队列中去。
    Queue队列,消息最终被送到这里等待消费者取走。
    Binding用于描述消息队列与交换机之间的关系。一个绑定就是基于路由键将交换机和消息队列连接起来的路由规则。因此可以将交换器看成一个由绑定构成的路由表
    Routing Key路由规则,可用来确定如何路由一个特定消息。
    Message消息
    Publisher消息发布者
    Consumer消费者

4. RabbitMQ

  1. 官网

RabbitMQ概述

​ RabbitMQ 拥有成千上万的用户,是最受欢迎的开源消息代理服务器之一;

​ RabbitMQ 是一个开源的 AMQP(高级消息队列协议)实现,服务器端用 Erlang 语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,用于在分布式系统中存储、转发消息。

5. RabbitMQ 简单模式

# 启动
systemctl start rabbitmq-server
# 停止
systemctl stop rabbitmq-server
# 重启
systemctl restart rabbitmq-server
# 查看状态
systemctl status rabbitmq-server
# 设置开机启动
systemctl enable rabbitmq-server
# 取消开机启动
systemctl disable rabbitmq-server
  • P是生产者。
  • C是消费者。
  • 中间的Queue区域是一个队列,表示消息缓冲区。

6. RabbitMQ 工作队列模式

​ 在使用消息系统时,一般情况下生产者往队列里插入数据的速度是比较快的,但是消费者消费数据往往涉及到一些业务逻辑处理导致速度跟不上生产者生产数据的速度。因此,如果一个队列只有一个消费者的话,很容易导致大量的消息堆积在队列里,这时,就可以使用工作队列,这样一个队列可以有多个消费者同时消费数据。

​ 当队列有多个消费者时,消息会被哪个消费者消费呢?这里主要有两种模式:

  1. 轮询分发:一个消费者消费一条;
  2. 公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;

6.1 轮询分发

​ 一个消费者消费一条,平均分配;

6.2 公平分发

​ 根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;

  1. 消费者需要手动确认消息
  2. 设置消费者每次取一条消息,channel.basicQos(1);

7. 消费者确认模式(ACK 机制)

  1. 什么是消费者消息确认?

    为了确保消息不会丢失,RabbitMQ 支持 消息确认,消费者发回确认消息,告诉 RabbitMQ 特定消息已被接收、处理,并且 RabbitMQ可以自由删除它。

  2. RabbitMQ提供了两种确认模式

    自动确认

    手动确认

7.1 自动确认

​ 自动确认表示消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有成功处理这条消息,那么就相当于丢失了消息;

7.2 手动确认

​ 如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,此时,RabbitMQ会将消息重新放入队列中。

​ 开启手动确认后,如果队列有多个消费者,当出现异常情况后,RabbitMQ会立即将这个消息推送给另一个在线的消费者,这种机制保证了在消费方不会丢失消息。

7.3 忘记确认消息

​ 忘记进行消息确认是一个简单的错误,但后果很严重,当客户端(消费者)退出时,消息将被重新传送(这可能看起来像随机重新传送)到RabbitMQ队列,RabbitMQ会消耗越来越多的内存,因为它无法释放任何未确认的消息,这时就会造成内存泄漏。

8. RabbitMQ 交换机

​ RabbitMQ 消息传递模型的核心思想是:生产者从不直接向队列发送任何消息。实际上,生产者甚至根本不知道消息是否会被传送到队列,相反,生产者只能将消息发送到交换机。交换机一方面接收来自生产者的消息,另一方面将它们推送到队列中。

​ 交换机必须确切地知道如何处理它收到的消息,它应该发送到特定队列中?还是应该发送到许多队列中?或者它应该被丢弃?这由交换类型决定。

8.1 交换机类型

​ 交换机类型:fanoutdirecttopicheaders

8.2 fanout 交换机(实现发布-订阅模式)

  • 发布-订阅模式:一条消息可以被多个消费者同时消费

    ​ 在RabbitMQ中,一个队列上的一条消息只能被一个消费者消费,要实现一条消息被多个消费者同时消费,需要借助多个队列(也就是需要将这一条消息发送到多个队列)

教程地址

​ fanout交换非常简单,它仅将收到的所有消息广播到所有队列。

​ fanout交换机不设置路由键,我们只需要将队列绑定到交换机上,生产者发送到fanout交换机的消息都会被转发到与该交换机绑定的所有队列上,很像子网广播,每台子网内的主机都能获得了一份消息。

​ 交换机和队列之间产生关联需要使用binding,可理解为交换机只向与其绑定的队列中发送消息,这里的绑定关系需要使用Routing Key;

​ 相对比fanout交换机,direct交换机在其基础上,多加了一层秘钥(Routing Key)。

8.4. Topic交换机

​ topic 交换机是在 direct 交换机的基础上,支持了对 routing key 的通配符匹配(*号和#号),以满足更加复杂的消息分发场景。

*:精确匹配一个词(一个字母、一个单词)

#:匹配零个或多个词(多个字母、多个单词,都以**"点"**分割)

注:

  • 如果队列绑定的路由键是#,那么这个队列可以接收所有数据,就类似于fanout交换机了

  • 如果队列绑定的路由键键当中没有#*,那么类似direct交换机了

1、创建工程

  1. rabbitmq-registry pom
  2. rabbitmq-common ResultVO、Email 对象
  3. registry-service Spring Boot 注册服务(注册信息入库、发送mq消息)
  4. email-service Spring Boot 邮件下发服务(监听队列,发送邮件)

2、注册接口

  1. 接口地址:http://localhost:8080/user/reg

  2. 请求方法:post

  3. 请求参数:

    {
       "username": "zs01",
    	"email": "431103832@qq.com",
       "age": "11"
    }
    
  4. 响应结果

    {
       "code": 200,
       "message": "success",
       "data": null
    }
    

3、配置邮件交换机、队列

  1. 在 user-service 中配置邮件队列

    @Configuration
    public class EmailQueueConfig {
    
       public static final String EXCHANGE = "email";
    
       public static final String QUEUE = "email";
    
       public static final String KEY = "email";
    
       @Bean
       public DirectExchange emailExchange() {
          return new DirectExchange(EXCHANGE);
       }
    
       @Bean
       public Queue emailQueue() {
          return new Queue(QUEUE);
       }
    
       @Bean
       public Binding emailBinding(Queue emailQueue, DirectExchange emailExchange) {
          return BindingBuilder.bind(emailQueue).to(emailExchange).with(KEY);
       }
    }
    
  2. 发送消息到队列

    {
    	"receiver": "注册邮箱",
    	"subject": "邮件主题",
    	"content": "邮件内容"
    }