RabbitMQ

本文详细介绍了消息中间件RabbitMQ,从概念到安装,再到运行机制和使用方法,包括exchange类型的创建、绑定队列、消息的发送与接收、可靠抵达机制以及消息消费端的确认。重点讲解了如何通过json序列化发送和接收消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 消息中间件概述

1.1. 什么是消息中间件

MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。多用于分布式系统之间进行通信。


为什么使用MQ:
   在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从
   而提高了系统的吞吐量。
   
   1、任务异步处理:
     将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
     
   2、应用程序解耦合:
     MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
     比如订单系统要远程调用库存系统、支付系统、物流系统,这样会耦合,修改参数时候麻烦。
     使用消息队列后,订单系统给消息MQ发送一条消息就算成功了
     
   3、削峰填谷:
     如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。
     低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡住,甚至宕机。
     
     
 使用mq后,在大并发下,比如每秒1000个数据写入数据库,那么就会有大量的消息数据积压在mq中,高峰就被削掉了,不会有大量请求打到数   据库,在经过高峰期后,消费消息的速度依然保持不变,那么被积压的消息就会被消费完,这叫做填谷。	

1.2.消息目的地

1.队列(queue):点对点消息通信 (point-to-point)
  --消息发送者发送消息,消息代理将其放入一个队列中,消息接受者从队列获取消息内容,
    消息读取后被移出队列
    
  --消息只有唯一的发送者和接收者,但并不是说只能有一个接收者(指的是消息只能被众多的消费者者     中的其中一个消费)

2.主题(topic):发布(publish) / 订阅(subscribe)消息通信

  --发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时     同时接收消息

1.3.消息通信的模型

MQ是消息通信的模型;
   实现MQ的大致有两种主流方式:AMQP、JMS。
   
  JMS(java Message service)java消息服务:
    --是一个Java平台中关于面向消息中间件(MOM)的API
    --基于jvm消息代理的规范。ActiveMq,HornetMq是JMS实现的。
    
   
  AMQO(Advanced Message Queuing Protocol):
    --是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,
      并不受客户端/中间件不同产品,不同的开发语言等条件的限制
    
    --AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议),
      这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式,类比HTTP。
      
    --高级消息队列协议,也是一个消息代理的规范,兼容JMS
    --RabbitMQ是AMQP的实现。

1.4.通信模型的区别

 

 

 

2.RabbitMQ概念

2.1.message

消息他又消息头和消息体组成。消息体是不透明的,而消息头则是由一系列的可选属性组成,这些属性包括route-key(路由键),priority(相对于其他信息的优先权),delivery-mode(指出该消息可能需要持久性存储)等

2.2.publisher

消息的生产者,也是一个向交换器发布消息的客户端应用程序

2.3.exchange

交换器,用来接收生产者发送的消息并将消息路由给服务器中的队列。
exchange有四种类型:direct(默认),fanout,topic和headers。
不同类型的exchange转发的消息的策略有所区别

2.4.queue

消息队列,用来保存消息直到发送给消费者,它是消息的容器,也是消息的终点,一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接达到这个队列将其取走。

2.5.binding

绑定,用于消息队列和交换器之间的关联,一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

exchange和queue的绑定可以是多对多的关系

2.6.connection

网络连接,比如一个tcp连接

2.7.channel

信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的tcp连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息,订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁tcp都是非常昂贵的开销,所以引入了信道的概念,以复用一条tcp连接。

2.8.consumer

消息的消费者,,表示一个从消息队列中取得消息的客户端应用程序

2.9.virtual host

虚拟主机,表示一批交换机,消息队列和相关对象。虚拟主机是贡献相同的身份认证和加密环境的独立服务器域,每个虚拟主机本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列,交换器,绑定和权限机制,虚拟主机是AMQP概念的基础,必须在连接时指定,RabbitMQ默认的虚拟主机是/ 

2.10.broker

表示消息队列服务器实体

2.11.流程图

 

其中虚拟主机就是将broker给隔离起来,就是共用一个MQ服务,但是使用不同的虚拟主机,这样如果有多个应用:一个java,一个php,他们虽然共用一个Mq的服务器,但是虚拟主机不同,他们的操作不会互相影响。如下图:

 

3.安装RabbitMq

使用doker的方式进行安装

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management

 

首先web的管理后台,我们需要开放防火墙,阿里云的esc服务器,我们需要配置安全组。
默认的账号密码是:guest

 

其中5672是更mq打交道的端口,收发消息的端口。
25672是集群环境的端口。
15672是访问mq的web后台的接口。
之前说的增加虚拟主机:如图

 

4.运行机制

AMQP中的消息路由
 消息的路由过程和java开发者熟悉的jms存在一些差别,AMQP中增加了Exchange和binding的角色。
 生产者把消息发布到exchange上,消息最终到达队列并被消费者接收,而binding决定交换器的消息应该发送到哪个队列。

4.1.exchange类型

exchange分发消息时根据根据类型的不同分发策略有区别,目前四种类型:
direct(默认),fanout,topic和headers。

其中direct(默认)和headers(不使用)都是点对点的
fanout,topic都是主题发布订阅的。

headers匹配AMQP消息的header而不是路由键,headers交换器和direct交换器完全一致,但性能差很多,几乎用不到。

4.1.1.direct

消息中的路由键值如果和binding中的binding key一致,交换器就将消息发到对应得队列中。
路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为"dog",则只转发路由键标记为"dog"的消息,不会转发"dog.puppy",也不会转发"dog.guary"等等,它是完全匹配,单播的模式。

这种可以理解为定向

4.1.2.fanout

每个发到fanout类型交换器的消息都会分析到所有绑定的队列上去,fanout交换器不处理路由键,知识简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上,就很像子网传播,每台子网内的主机都获得了一份复制的消息,fanout类型转发消息是最快的。

这种可以理解为广播。

4.1.3.topic

topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上,它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个统配符:符号"#"和符号"*",

#匹配0个或多个单词,*匹配一个单词。


这种是匹配模式。也就是通配符。

4.2.创建交换器

默认提供了7个交换机

 

自己创建时:

type选择需要使用的类型。

Durability的选项:Durable是持久化,另一个是非持久化,重启就没了。

其他的默认。
Arguments是设置参数,我们暂时不设置。

 

4.3.绑定队列

        需要先创建队列

 

然后回到exchange交换器中,点击交换器的名称。
注意,交换器可以绑定队列,也可以绑定其他的交换器。
还要写上路由键。

 

 注意:创建的顺序,是先创建交换器,在创建队列,在将交换器绑定队列,然后提供路由键(来路由队列的)

5.使用

首先我们在Test里面先玩一下

导入依赖:
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        
        
配置文件:
spring.rabbitmq.host=120.77.47.129  #mq地址
spring.rabbitmq.port=5672  #端口号
spring.rabbitmq.virtual-host=/   #虚拟主机路径


启动类加注解:
@EnableRabbit

5.1.开始

首先创建交换器
找到
org.springframework.amqp.core.Exchange接口,看他的子类实现。

抽象子类   org.springframework.amqp.core.AbstractExchange

继续看子类实现

我们可以看到,他有五个实现子类,
除了四个我们我们可以上面接触过的,还有一个是CustomExchange 是一个自定义的交换器

我们暂且使用DirectExchange

5.1.1创建交换器

   

注入
@Autowired
RabbitAdmin rabbitAdmin;

 其他重载方法:

 

name:交换机的名称
durable: 是否持久化   (在mq的web后台中,这个属性有两个值 1.Durable(持久),2.Transient(瞬态))
autoDelete: 是否自动删除
arguments: 参数,自己想添加什么参数就添加什么

第一个方法传入名称后,还设置了两个参数为true和false,就是持久化存储和非自动删除。

5.1.2创建队列

 看其他的Queue的重载方法

 

name: 队列的名称
durable: 是否持久化   (在mq的web后台中,这个属性有两个值 1.Durable(持久),2.Transient(瞬态))
exclusive: 表示在被一个连接连接后,不能被其他的连接所连接,我们设置为false
autoDelete: 是否自动删除
arguments: 参数,自己想添加什么参数就添加什么

当交换器和队列都创建好,我们要让他们产生联系,那就是绑定,绑定后据可以通过路由来访问。

5.1.3.交换器绑定队列

 

destination: 目的地,可以是队列,也可以是其他的交换器
Binding.DestinationType destinationType: 是绑定的类型 有QUEUE,EXCHANGE两种,我们需要根据目的地类型来指定
exchange: 目前需要绑定的交换器。
routingKey:路由键
arguments:和上面的交换器和队列一样的,传入的参数,一般我们不传入。

这样我们就将交换器和队列已经绑定关系都创建好了

接下来,我们可以来发送消息

5.2.收发消息

5.2.1.发送消息

首先注入模板

@Autowired
RabbitTemplate rabbitTemplate;


这些springboot都帮我们做好了,拿来使用就可以了

 补充一下,并不是只能在单元测试里面,这里只是图方便。

第一个参数是交换器的名称,第二个是路由键,第三个是发送的消息类型(这里是发送的对象)

注意:发送的是对象的话我们需要让对象实现序列化接口,否则报错,字符串或数字无所谓。

消息发送成功后:

查看rabbit的web后台

 我们发现是一串序列化过后的,这是默认的jdk序列化,但是我们不需要jdk序列化,

 我们要json序列化应该怎么办。

打开RabbitAutoConfiguration类,发现里面有这样的配置

 就是有一个这样的消息转换器,不为空需要我们设置,但是默认是没有的。

接着看下图:

这里有一个json的转换器,我们可以使用它。

使用之前我们需要导入jackson的坐标:

       <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.11.0</version>
        </dependency>

 然后创建一个转换器

 此时在发送消息:

 就是一个json字符串。

5.2.2.接收消息

可以携带这个几个参数,比如,Cilent是我们发送消息时的消息类型,如果不这样写,就需要自己手动转换,比较麻烦。直接写参数的化,springboot会帮我们自动转化成这个类型的消息。

注解@RabbitListener的queues是一个数组,它可以监听多个队列。

业务场景:

1.当是分布式系统时,有多个服务端都监听了该队列,也就是都有相同的代码,那么消息是怎么处理的?
  答:只有一个服务端会拿到消息来处理
  
2.那么当有很多消息到了mq,是怎么来监听消费消息的呢?
  答:会把当前的消息处理完,会把一个消息完全处理完,也就是方法运行结束,才会接收新的消息。

5.2.3.发送不同的消息怎么接收

上面我们提到了@RabbitListener注解,这个注解是可以使用到类上还有方法上的
还有一个接收消息的注解
@RabbitHandler  它是使用在方法上的。


引出问题,如果我的这个队列里面根据业务存了不同类型的消息,我们应该如何接收。
我们就可以使用这两个注解相结合的方式来接收消息。


1.首先我们发送不同类型的消息,这里就不举例了,
然后看控制台的打印效果。

 我们使用@RabbitHandler来监听不同的消息类型,需要搭配@RabbitListener注解才可以。

6.消息确认

6.1.可靠抵达

.为保证消息不丢失,可靠抵达可以使用事务消息,但性能下降250倍,因此引入确认机制

.publisher confirmCallback 确认模式
.publisher returnCallback 未投递到queue退回模式
.consumer  ack机制

 

confirmCallback:
    只能表示被broker代理服务器接收到了,不能代表它已经存放到队列里了。
    只有被broker代理接收到了消息就会有返回


returnCallback:
    是Exchange投递到队列里面未成功才会返回信息的。

 

6.1.1.使用

 在配置文件中再加上下面的配置,否则是失效的


spring.rabbitmq.publisher-confirms=true       #开启发送端发送到borker确认          (confirmCallback)

spring.rabbitmq.publisher-returns=true        #开启Exchange投递到队列的消息确认     (returnCallback)

spring.rabbitmq.template.mandatory=true       #只要抵达不了队列,以异步发送优先回调到returnconfirm

写一个配置类,就是使用上面的两个结合起来来确定消息是否投递成功:

@Configuration
public class MyRabbitMQConfig {


    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct  //表示当MyRabbitMQConfig对象创建完成以后,执行这个方法
    public void initRabbitTemplate(){
        //设置确认回调,是发送端发送到borker是发送成功回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
            /**
             * @param correlationData 当前消息的唯一关联属性(消息的唯一id)
             * @param ack  消息是否成功收到
             * @param cause  失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("id:"+correlationData+",ack="+ack);
            }
        });

        //投递,是Exchange投递到队列失败时回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {

            /**
             * @param message  投递失败的消息的详细信息
             * @param replyCode  回复的状态码
             * @param replyText  回复的文本内容
             * @param exChange   当时这个消息发送的是哪个交换机
             * @param routingKey 当时这个消息用的哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exChange, String routingKey) {
                System.out.println("correlationData="+message+",replyCode="+replyCode+",replyText="+replyText+",exChange="+exChange+",routingKey="+routingKey);
            }
        });
    }
}

为了方便看,直接截个图:

 

 上面代码中消息的id由于我们没有设置,是获取不到的,需要我们在发送消息时设置id,就是最后的那个参数

 

说完了消息投递,那么该说消费了。

6.2.消息消费端确认

 

像我们上面的消费者接收到的消息就会被mq移除队列,因此默认的签收的
问题:
就是如果监听的队列存入了多个数据,我们的监听的方法所在的服务器宕机了,,尽管还没有处理完数据(按理来说应该处理的才移除,但是我们发现没有处理的也被移除了),mq队列的消息已经丢失了,因为这是自动确认的

解决,手动签收,处理一个数据,签收一个。

6.2.1.使用

配置文件添加属性

spring.rabbitmq.listener.direct.acknowledge-mode=manual
总共有三个值:
manual  手动
none 
auto  自动(默认不写这个配置就是这个,默认的)

这里我们就模仿一下没有签收的情况和签收的情况

 

开启了手动签收,就算处理消息时中断,那么消息也不会丢失,除非主动写了丢弃消息。
当然这里只是个例子而已,使用了这种方式来处理丢失消息。

未完....

### RabbitMQ 使用指南和教程 #### 安装 RabbitMQ RabbitMQ 是一种基于 AMQP 协议的消息中间件,用于实现分布式系统的可靠消息传递。以下是安装 RabbitMQ 的基本流程: 1. **安装 Erlang** RabbitMQ 基于 Erlang 编程语言开发,因此需要先安装 Erlang 运行环境。可以通过包管理器或者下载官方二制文件完成安装。 2. **安装 RabbitMQ Server** 下载并安装 RabbitMQ Server 软件包。对于 Linux 用户,可以使用以下命令: ```bash sudo apt-get install rabbitmq-server ``` 3. **启动服务** 启动 RabbitMQ 服务后,默认监听端口为 `5672`(AMQP 协议),Web 管理界面默认运行在 `15672` 端口上。 ```bash sudo systemctl start rabbitmq-server ``` 4. **启用 Web 管理插件** 可通过以下命令启用 RabbitMQ 提供的 Web 管理工具: ```bash sudo rabbitmq-plugins enable rabbitmq_management ``` --- #### 配置用户与权限 为了安全访问 RabbitMQ 实例,通常需要创建自定义用户并分配相应权限。 - 创建新用户: ```bash rabbitmqctl add_user rabbitmq 211314 ``` 此操作会新增名为 `rabbitmq` 的用户,并将其密码设为 `211314`[^1]。 - 设置用户角色: ```bash rabbitmqctl set_user_tags rabbitmq administrator ``` 将该用户的标签设定为管理员角色,使其拥有完全控制权[^1]。 - 授予用户权限: ```bash rabbitmqctl set_permissions -p "/" rabbitmq ".*" ".*" ".*" ``` 上述命令授予用户对根虚拟主机 `/` 中所有资源的操作权限[^1]。 - 查看现有用户及其角色: ```bash rabbitmqctl list_users ``` --- #### 集群配置 RabbitMQ 支持多种集群模式来提升可用性和性能。主要分为两类:普通模式和镜像模式。 - **普通模式** 在这种模式下,各节点独立储队列中的数据和其他元信息(如交换机)。当客户端尝试消费某个不在当前连接节点上的消息时,目标节点会被请求转发所需的数据[^2]。 - **镜像模式** 对比之下,在镜像模式中,指定队列的内容将在多个节点间保持一致副本。即使部分成员失效,剩余活节点仍能继续提供完整的服务功能[^2]。 > 注意事项:尽管镜像模式提高了可靠性,但也带来了额外开销——网络流量增加以及写入延迟上升等问题需被充分考虑去。 --- #### 应用集成示例 假设要在一个 Java 或 Python 应用程序里利用 RabbitMQ 来发送/接收消息,则可能涉及以下几个步骤: 1. **声明交换器 (Exchange)** 和绑定关系: ```java channel.exchangeDeclare(exchangeName, "direct", true); channel.queueDeclare(queueName, true, false, false, null); channel.queueBind(queueName, exchangeName, routingKey); ``` 如此一来便完成了持久化队列及路由键关联工作[^3]。 2. 发布一条测试消息至上述已建立好的通道路径之中; 3. 订阅对应主题下的事件流以便实时捕获最新动态更新情况; --- #### 总结 以上涵盖了从基础安装到高级特性使用的整个过程概述。希望这些指导能够帮助您快速掌握如何部署与维护属于自己的 RabbitMQ 平台实例! 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值