RabbitMQ学习笔记

前置知识

1. 虽然天天用@RestController,但今天才深刻体会到了与@Controller的区别

2. 终于明白了构造函数传参到底是怎么传的

RabbitMQ之旅

RabbitMQ简介

AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品、不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。

2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。 Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

MQ的优势

1 异步解耦

以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的订单被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,下单用户感受不到物流系统的故障,提升系统的可用性。

2 削峰填谷

举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。

3 消息分发

在实际开发中一个系统的数据有的时候需要分发个不同的系统中, 拿电商举例,在双11的时候有很多会场,每一个会场可能都需要用到一个商品的数据,那么我们需要把数据分发到不同的会场中,假设又加了一个会场我们还需要把数据分发给新的会场。而使用了MQ后,仅需要把消息发送到消息队列中,谁用谁自取即可。

MQ的劣势

1 系统可用性降低

系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?

2 系统复杂度提高

MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?

3 一致性问题

A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?

RabbitMQ中的核心概念

Broker

接收和分发消息的应用,RabbitMQ Server就是 Message Broker

Virtual host

出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等。

Connection

publisher/consumer 和 broker 之间的 TCP 连接。

Channel

如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。

Exchange

message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。

Queue

消息最终被送到这里等待 consumer 取走。

Binding

exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。

RabbitMQ常见的几种工作模式

1、简单队列模式 Simple Queue

一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。

2、工作队列模式 Work Queue

一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。

3、发布订阅模式 Publish/subscribe

需要设置类型为 fanout 的交换机 ,并且交换机和队列进行绑定 ,当发送消息到交换机后,交换机会将消息发送到绑定的队列。

4、路由模式 Routing

需要设置类型为 direct 的交换机 ,交换机和队列进行绑定 , 并且指定 routing key,当发送消息到交换机后 ,交换机会根据 routing key 将消息发送到对应的队列。

5、通配符模式 Topic

需要设置类型为 topic 的交换机 ,交换机和队列进行绑定 ,并且指定通配符方式的 routing key ,当发送消息到交换机后 ,交换机会根据 routing key 将消息发送到对应的队列。

RabbitMQ高频面试题

1.RabbitMQ如果出现消息重复消费怎么解决

什么是重复消费?

返回失败
返回失败了

采用幂等性解决:幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。

解决方案:加一个日志流水表

2 RabbitMQ中的死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

消息成为死信的三种情况:

  1. 队列消息长度到达限制;

  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

3 RabbitMQ 怎么实现消息可靠性

生产者投递可靠性

要实现生产者投递的可靠性,可以采取以下几个步骤:

  1. 开启 Confirm 模式:在 RabbitMQ 中,可以将生产者设置为 Confirm 模式,以确保消息的可靠投递。Confirm 模式是一种轻量级的分布式事务机制,当消息被 RabbitMQ 成功接收并持久化后,会返回一个确认消息(ack)。如果消息发送失败,则会返回一个拒绝消息(nack),此时可以进行相应的处理。

    channel.confirmSelect();
  2. 发送消息并等待确认:在发送消息之后,等待 RabbitMQ 返回确认消息或拒绝消息。可以使用 waitForConfirms() 方法来进行阻塞等待,也可以使用 addConfirmListener() 方法来注册确认监听器,异步处理确认结果。

    channel.basicPublish(exchange, routingKey, null, messageBody); if (channel.waitForConfirms()) { // 消息成功被 RabbitMQ 接收并持久化 } else { // 消息发送失败 }

    或者

    channel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long deliveryTag, boolean multiple) { // 消息被确认 } @Override public void handleNack(long deliveryTag, boolean multiple) { // 消息被拒绝 } }); 
  3. 处理确认和拒绝:根据接收到的确认和拒绝消息,进行相应的处理。对于确认消息,可以认为消息已经成功发送;对于拒绝消息,可能是由于网络问题或 RabbitMQ 繁忙导致的发送失败,可以进行重试或其他的错误处理。

    channel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long deliveryTag, boolean multiple) { // 消息被确认,可以进行相应的处理 } @Override public void handleNack(long deliveryTag, boolean multiple) { // 消息被拒绝,可以进行重试或其他错误处理 } });
  4. 设置 Mandatory 标志位:如果消息无法被路由到任何队列,可以通过设置 mandatory 标志位为 true,使得消息被退回(Return)。然后可以通过 addReturnListener 来监听返回的消息,并进行相应的处理。

    String message = "Hello, RabbitMQ!"; channel.basicPublish(exchangeName, routingKey, true, null, message.getBytes());                 channel.addReturnListener(new ReturnListener() { @Override public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException { // 处理被退回的消息 } });

通过以上步骤,可以增加生产者消息投递的可靠性。注意,在使用 Confirm 模式时,需要消耗额外的资源和性能,因此在大规模高并发的场景中,需要进行合理的性能测试和调优。同时,还可以考虑使用持久化、重试、限流等策略来进一步提高消息投递的可靠性。

消费者投递可靠性

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。

有三种确认方式:

• 自动确认:acknowledge="none"

• 手动确认:acknowledge="manual"

• 根据异常情况确认:acknowledge="auto",(这种方式使用麻烦,不作讲解)

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

在 RabbitMQ 中,basic.nackbasic.reject 都用于消费者告诉 RabbitMQ 某条消息没有成功处理,但是它们有一些关键的区别。理解这些区别有助于更好地设计消息处理逻辑。

basic.nack

basic.nack(negative acknowledgment)是在消费者无法处理消息时通知 RabbitMQ 的方法。其主要功能和特点如下:

  • 批量操作:可以一次性拒绝多条消息。
  • 重回队列:提供一个参数来决定是否将消息重新放回队列中。
  • 部分拒绝:可以选择拒绝单条消息或消息的多个部分。

方法签名:

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

  • deliveryTag:消息的唯一标识符。
  • multiple:如果设为 true,表示拒绝所有小于或等于 deliveryTag 的消息。
  • requeue:如果设为 true,表示将消息重新放入队列;如果设为 false,消息将被丢弃或发送到死信交换器(如果设置了 DLX)。

示例:

channel.basicNack(envelope.getDeliveryTag(), false, true); 
basic.reject

basic.reject 也是用于消费者告诉 RabbitMQ 某条消息没有成功处理,但其功能相对简单。主要特点如下:

  • 单条消息:只能拒绝单条消息。
  • 重回队列:提供一个参数来决定是否将消息重新放回队列中。
  • 无批量操作:不能一次性拒绝多条消息。

方法签名:

void basicReject(long deliveryTag, boolean requeue) throws IOException;

  • deliveryTag:消息的唯一标识符。
  • requeue:如果设为 true,表示将消息重新放入队列;如果设为 false,消息将被丢弃或发送到死信交换器(如果设置了 DLX)。

4 RabbitMQ 如何实现延迟队列

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费

需求:

  1. 下单后,30分钟未支付,取消订单,回滚库存。

  2. 新用户注册成功7天后,发送短信问候。

实现方式:

  1. 定时器

  2. 延迟队列

使用延时队列实现

  1. 延迟队列 指消息进入队列后,可以被延迟一定时间,再进行消费。

  2. RabbitMQ没有提供延迟队列功能,但是可以使用 : TTL + DLX 来实现延迟队列效果。

附录

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

//生产者类

@Service
public class MessageProducer {

    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public MessageProducer(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void sendMessage(String message) {
        String exchange = "boot_topic_exchange";
        String routingKey = "order.created";
        rabbitTemplate.convertAndSend(exchange, routingKey, message);
        System.out.println("已发送消息: " + message);
    }
}
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.support.AmqpHeaders;
import com.rabbitmq.client.Channel;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;


//消费者类

@Component
public class TopicReceiver01 {

    /**
    * value = @Queue("boot_topic_queue01"):声明了一个名为boot_topic_queue01的队列。如果                          这个队列不存在,它会被创建。
exchange = @Exchange(name = "boot_topic_exchange", type = "topic"):声明了一个名为boot_topic_exchange的交换机,类型为topic。如果这个交换机不存在,它会被创建。
key = "order.*":指定了路由键模式order.*,意味着这个监听器会接收所有路由键以order.开头的消息。
    */

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("boot_topic_queue01"),
            exchange = @Exchange(name = "boot_topic_exchange", type = "topic"),
            key = "order.*"
    ))
/*
String msg:接收到的消息内容,自动转换为String类型。
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag:从消息头部获取的交付标签,用于确认消息(acknowledge)。
Channel channel:当前消息使用的AMQP通道,允许你对消息进行确认等操作。
*/

    public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws Exception {
        System.out.println("topic收取消息:" + msg);
        channel.basicAck(deliveryTag, false);

    /*
    deliveryTag:消息的交付标签,用于唯一标识该消息。
false:表示仅确认当前消息(如果为true,则确认所有小于或等于当前deliveryTag的消息)。
    */
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值