RabbitMQ学习笔记

本文介绍了MQ消息队列的基本概念及其在分布式系统中的作用,包括实现应用解耦、提升系统响应速度及处理高峰流量等功能。

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

什么是MQ?

Message Queen(MQ)消息队列

为了解决分布式系统之间的通信问题。

我们原本的单体架构应用就好比一家人在一起干活,而现在的分布式架构应用就可以理解为分家了,每个人分开干活,但是这每个人之间必须可以通信才可以更好的干活。

分布式系统通信的模式有两种:直接调用模块,借助第三方完成通信。而我们MQ就是第三方,消息先发给MQ,再由MQ发给其他模块服务。

RabbitMq架构图

  • publisher:生产者,也就是发送消息的一方

  • consumer:消费者,也就是消费消息的一方

  • queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理

  • exchange:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。

  • virtual host:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue

 

为什么要学MQ?

1、应用解耦

用户发起下订单的请求,请求先来到订单系统,下订单需要调用支付系统完成支付操作,调用库存系统更新库存,调用物流系统更新物流信息。

而如果没有用消息队列,订单系统直接耦合调用这三个系统的话,如果有一个系统出错了,就会导致下订单出错。

现在有了MQ消息队列的话,订单系统就和其他系统分开,解耦合。订单系统接收到下订单的请求后只需要将请求消息发给消息队列中后,就可以返回下订单成功的消息给用户, 后续的支付、库存、物流等操作,由MQ将消息发给他们,让他们完成相应的操作,就算其中有个系统出错了,过一段时间好了也会继续完成工作的。就是订单下完直接返回成功,后面的操作肯定早晚会完成的。

2、异步提速

在没有用MQ之前,用户下订单后,下订单需要先调用库存系统更新库存后,再调用支付系统完成支付操作,最后调用调用物流系统更新物流信息,这些操作是同步完成的,用户要一直等这些操作完成才能完成下订单操作。要花20+300+300+300=920ms

可见请求处理的速度大大提升了,而且单位时间处理的请求数也变多了(系统吞吐量)。

 有了MQ之后,用户下订单,订单系统只需要将消息发给MQ然后保存数据到数据库后,就可以返回下单成功的信息,至于后面的库存、支付、物流的操作,后面它们自己可以从MQ中取消息完成操作,对于客户来说是透明的。只需要花20+5=25ms

 3、消峰填谷

举个例子,如A系统每秒最多能处理1000次请求,这个处理能力应付正常时段绰绰有余,正常时段我们发送请求一秒后就能返回结果。但是在高峰期,如果一秒钟之内有5000请求发给A系统,这时候A系统就会直接坏掉,这样就不行。使用消息队列做缓冲,把5000个请求都发给MQ,这些请求消息依次在MQ中排队,先到先出来发给A系统消费,每秒发送1000个请求给A系统处理。

对于A系统来说就是消峰了,从5000次请求消到1000次。大量请求数据堆积在MQ中,直到消费完堆积的消息,就叫填谷。

安装

Erlang和RabbitMq版本关系:Erlang Version Requirements | RabbitMQ

安装问题

解决rabbitmq启动报错“node ‘rabbit‘ not running at all”_node 'rabbit' not running at all-CSDN博客

卸载RabbitMq使用管理员安装

linux

docker run \
 -e RABBITMQ_DEFAULT_USER=itheima \
 -e RABBITMQ_DEFAULT_PASS=123321 \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 -p 15672:15672 \
 -p 5672:5672 \
 --network hm-net\
 -d \
 rabbitmq:3.8-management

RabbitMq生产者

RabbitMq交换机

负责将收到来自生产者的消息路由发送给对应的消息队列,如果路由失败,消息将丢失,交换机不存储消息。

Fanout

  • Fanout:广播,将消息交给所有绑定到交换机的队列。

package com.itheima.consumer.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfig {
    /**
     * 声明交换机
     * @return Fanout类型交换机
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("hmall.fanout");
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

 消息发送和接收

@Test
public void testFanoutExchange() {
    // 交换机名称
    String exchangeName = "hmall.fanout";
    // 消息
    String message = "hello, everyone!";
    rabbitTemplate.convertAndSend(exchangeName, "", message);
}


@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
    System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}

@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
    System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}

Direct

  • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列

package com.itheima.consumer.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectConfig {

    /**
     * 声明交换机
     * @return Direct类型交换机
     */
    @Bean
    public DirectExchange directExchange(){
        return ExchangeBuilder.directExchange("hmall.direct").build();
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue directQueue1(){
        return new Queue("direct.queue1");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1WithRed(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }
    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1WithBlue(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue directQueue2(){
        return new Queue("direct.queue2");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2WithRed(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
    }
    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2WithYellow(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
    }
}

 发送消息和接收消息

@Test
public void testSendDirectExchange() {
    // 交换机名称
    String exchangeName = "hmall.direct";
    // 消息
    String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg) {
    System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg) {
    System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}

 Topic

  • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符。(可以实现广播和订阅)

通配符规则:

  • #:匹配一个或多个词

  • *:匹配不多不少恰好1个词

举例:

  • item.#:能够匹配item.spu.insert 或者 item.spu

  • item.*:只能匹配item.spu

 

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue1"),
    exchange = @Exchange(name = "hmall.topic", type = ExchangeTypes.TOPIC),
    key = "china.#"
))
public void listenTopicQueue1(String msg){
    System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue2"),
    exchange = @Exchange(name = "hmall.topic", type = ExchangeTypes.TOPIC),
    key = "#.news"
))
public void listenTopicQueue2(String msg){
    System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}

 

消息发送和接收 

/**
 * topicExchange
 */
@Test
public void testSendTopicExchange() {
    // 交换机名称
    String exchangeName = "hmall.topic";
    // 消息
    String message = "喜报!孙悟空大战哥斯拉,胜!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}

@RabbitListener(queues = "topic.queue1")
public void listenTopicQueue1(String msg){
    System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(queues = "topic.queue2")
public void listenTopicQueue2(String msg){
    System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}

 

  • Headers:头匹配,基于MQ的消息头匹配,用的较少。

RabbitMq消费者

手动确认消息

确认消息

void basicAck(long deliveryTag, boolean multiple) throws IOException;

手动确认消息 第一个参数是该消息的唯一标示值 false表示只删除该消息(true表示确认所有比指定的 deliveryTag 小或等于它的消息。)

拒绝消息

channel.basicNack(long deliveryTag, boolean multiple, boolean requeue):

deliveryTag:要拒绝的消息的唯一标识。
multiple:一个布尔值,如果设置为 true,则会拒绝该 deliveryTag 之前的所有未确认消息。
requeue:一个布尔值,如果设置为 true,则拒绝的消息会重新入队列,即放回到队列中,以便其他消费者再次处理。
使用 channel.basicNack 方法,你可以选择拒绝一条或多条消息,并选择是否重新入队。

channel.basicReject(long deliveryTag, boolean requeue):

deliveryTag:要拒绝的消息的唯一标识。
requeue:一个布尔值,如果设置为 true,则拒绝的消息会重新入队列,即放回到队列中,以便其他消费者再次处理。
使用 channel.basicReject 方法,你只能拒绝一条消息,但你可以选择是否重新入队。

RabbitMq机制

持久化

将消息和队列标记为持久化,旨在确保消息在 RabbitMQ 重启或故障时不会丢失。

持久化消息是指将消息数据存储到磁盘中,而不仅仅保存在内存中。

持久化队列是指队列的元数据被保存在磁盘中,以便 RabbitMQ 重启后能够恢复队列状态。

ps:将一个持久化消息发布到一个持久化队列中时,当RabbitMq重启时,持久化队列和持久化消息都会被恢复,确保消息不会丢失。

消息确认

confirm

由 RabbitMQ 发送给生产者的确认,表明消息已成功写入队列,用来保障生产者可靠的消息发送。

生产者在发送消息后,可以开启 confirm 模式,RabbitMq成功接收消息并存储后才会向生产者发送confirm确认。

ack

由消费者发送的确认,表明该消息已被成功处理,用来确保消费者可靠的处理消息。

消费者处理完消息,向RabbitMq发生ack,只有在收到 ack 后,RabbitMQ 才会删除该消息。可以设置为手动或自动确认。

retry

在 RabbitMQ 中,重试(retry)机制用于处理消费者处理消息失败的情况,以确保消息能够被重新尝试消费。

重试机制可以帮助确保消息最终会被成功处理,提高系统的可靠性。

1、使用死信队列

①当消费者处理消息失败并且达到最大重试次数后,可以将消息发送到一个死信队列。

②可以为死信队列设置 TTL(存活时间)或者使用其他策略,最终将其转发到原队列以实现重试。

2、延迟重试

可以使用 RabbitMQ 的延迟队列插件(如 rabbitmq_delayed_message_exchange),或通过创建一个延迟队列。

消息会被发送到延迟队列,在设置的延迟时间后,再转发到原队列。

消息TTL

设置消息的存活时间Time-To-Live,超时后会被自动删除

死信队列

用于处理未成功消费的消息,提供错误处理机制。

流量控制

防止消费者处理速度过慢导致内存占用过高。

集群(Clustering)

多个 RabbitMQ 实例可以组成一个集群,以提高可用性和扩展性。

使用

分布式事务

学习文章

RabbitMQ详解,用心看完这一篇就够了【重点】-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺着听Jay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值