学习笔记:分布式消息队列RabbitMQ

视频:【黑马程序员RabbitMQ全套教程,rabbitmq消息中间件到实战】 https://www.bilibili.com/video/BV15k4y1k7Ep/?share_source=copy_web&vd_source=94d49d29c066af15c2803ab959fb6297

分布式消息队列RabbitMQ

  • 一、基本概念
    • 1.1 消息队列
    • 1.2 RabbitMQ
    • 1.3 JMS
  • 二、基本使用
    • 2.1 RabbitMQ安装启动
    • 2.2 生产者实现
    • 2.3 消费者实现
  • 三、组成部分
  • 四、工作模式
    • 4.1 简单模式
    • 4.2 工作队列模式
    • 4.3 Pub/Sub 订阅模式
    • 4.4 Routing 路由模式
    • 4.5 Topics 通配符模式
    • 4.6 工作模式总结
  • 五、高级特性
    • 5.1 消息的可靠性投递
    • 5.2 Consumer Ack
    • 5.3 Consumer 限流
    • 5.4 TTL 消息存活时间
    • 5.5 死信队列
    • 5.6 延迟队列
    • 5.7 日志与监控
    • 5.8 消息追踪
  • 六、应用问题
    • 6.1 消息可靠性保障
    • 6.2 消息幂等性保障


一、基本概念

消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。

“消息队列(Message Queue)”是在消息的传输过程中保存消息的容器,多用于分布式系统之间进行通信。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,消费者只负责从消息队列中取出数据处理

学习笔记:分布式消息队列RabbitMQ_第1张图片

1.1 消息队列

优点

消息队列主要有三个作用(优点):

  1. 应用解耦,提升容错性和可维护性。如下图所示:假设有系统B、C、D都需要系统A的数据,系统A调用三个方法发送数据到B、C、D。这时,系统D不需要了,那就需要在系统A把相关的代码删掉。假设这时有个新的系统E需要数据,这时系统A又要增加调用系统E的代码。为了降低这种强耦合,就可以使用MQ,系统A只需要把数据发送到MQ,其他系统如果需要数据,则从MQ中获取即可。

    学习笔记:分布式消息队列RabbitMQ_第2张图片

  2. 异步提速,提升用户体验和系统吞吐量(单位时间内处理请求的数目)。如下图所示:一个客户端请求发送进来,系统A会调用系统B、C、D三个系统,同步请求的话,响应时间就是系统A、B、C、D的总和,也就是800ms。如果使用MQ,系统A发送数据到MQ,然后就可以返回响应给客户端,不需要再等待系统B、C、D的响应,可以大大地提高性能。对于一些非必要的业务,比如发送短信,发送邮件等等,就可以采用MQ。

    学习笔记:分布式消息队列RabbitMQ_第3张图片

  3. 削峰填谷,提高系统稳定性。如下图所示:这其实是MQ一个很重要的应用。假设系统A在某一段时间请求数暴增,有5000个请求发送过来,系统A这时就会发送5000条SQL进入MySQL进行执行,MySQL对于如此庞大的请求当然处理不过来,MySQL就会崩溃,导致系统瘫痪。如果使用MQ,系统A不再是直接发送SQL到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取1000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃。

    学习笔记:分布式消息队列RabbitMQ_第4张图片
    使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。学习笔记:分布式消息队列RabbitMQ_第5张图片

缺点

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

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

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

应用

既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?

  1. 生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
  2. 容许短暂的不一致性。
  3. 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。

目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征,综合考虑。

学习笔记:分布式消息队列RabbitMQ_第6张图片

1.2 RabbitMQ

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

学习笔记:分布式消息队列RabbitMQ_第7张图片

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

学习笔记:分布式消息队列RabbitMQ_第8张图片

  • 可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。
  • 灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
  • 支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
  • 多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。
  • 支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。
  • 可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。
  • 插件机制。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 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html

1.3 JMS

  • JMS 即 Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件的API
  • JMS 是 JavaEE 规范中的一种,类比JDBC
  • 很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ 官方没有提供 JMS 的实现包,但是开源社区有

二、基本使用

2.1 RabbitMQ安装启动

通过docker启动rabbitmq-3.9-management容器:

docker pull rabbitmq:management

docker run -d \
-p 5672:5672 \
-p 15672:15672 \
-e RABBITMQ_DEFAULT_VHOST=/ziang-rabbitmq \
-e RABBITMQ_DEFAULT_USER=ziang \
-e RABBITMQ_DEFAULT_PASS=ziang \
--name ziang-rabbitmq
	rabbitmq:management

docker exec -it ziang-rabbitmq rabbitmq-plugins enable rabbitmq_management

此时可使用Chrome浏览器无痕浏览(最好使用无痕,否则可能会出现页面元素Error问题),输入localhost:15672进入RabbitMQ管理界面

2.2 生产者实现

public static void main(String[] args) throws IOException, TimeoutException {
    // 1.创建连接工厂
    ConnectionFactory factory = new ConnectionFactory();

    // 2.设置参数
    factory.setHost("localhost");      // 主机IP,默认localhost
    factory.setPort(5672);             // 主机端口,默认5672
    factory.setVirtualHost("/ziang-rabbitmq"); // 虚拟机,默认/
    factory.setUsername("ziang");      // 用户名,默认guest
    factory.setPassword("ziang");      // 密码,默认guest

    // 3.创建连接Connection
    Connection connection = factory.newConnection();

    // 4.创建通道Channel
    Channel channel = connection.createChannel();

    // 5.创建队列Queue
    /*
      queueDeclare(String queue, boolean durable,
                   boolean exclusive, boolean autoDelete, Map arguments);
      参数:
        1.queue:队列名称(如果不存在该队列,则会创建)
        2.durable:是否持久化。当MQ重启后消息是否仍存在(消息会持久化于erlang实现的数据库中)
        3.exclusive:
                * 是否独占(只能有一个消费者监听这个队列)
                * 当Connection关闭时,是否删除队列
        4.autoDelete:是否自动删除(当无Consumer时自动删除)
        5.arguments:其他参数
    */
    String queue = "hello_world";
    channel.queueDeclare(queue, true, false, false, null);

    // 6.发送消息
    /*
      basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body);
      参数:
        1.exchange:交换机名称。简单模式下交换机会使用默认的""
        2.routingKey:路由名称
        3.props:其他配置
        4.body:发送的消息数据
    */
    String msg = "Hello World!";
    channel.basicPublish("", queue, null, msg.getBytes(StandardCharsets.UTF_8));

    // 7.释放资源(可以选择性执行该步骤,观察rabbitmq_management信息变化)
    channel.close();
    connection.close();
}

2.3 消费者实现

public static void main(String[] args) throws IOException, TimeoutException {
    // 1.创建连接工厂
    ConnectionFactory factory = new ConnectionFactory();

    // 2.设置参数
    factory.setHost("localhost");      // 主机IP,默认localhost
    factory.setPort(5672);             // 主机端口,默认5672
    factory.setVirtualHost("/ziang-rabbitmq"); // 虚拟机,默认/
    factory.setUsername("ziang");      // 用户名,默认guest
    factory.setPassword("ziang");      // 密码,默认guest

    // 3.创建连接Connection
    Connection connection = factory.newConnection();

    // 4.创建通道Channel
    Channel channel = connection.createChannel();

    // 5.创建队列Queue
    /*
      queueDeclare(String queue, boolean durable,
                   boolean exclusive, boolean autoDelete, Map arguments);
      参数:
        1.queue:队列名称(如果不存在该队列,则会创建)
        2.durable:是否持久化。当MQ重启后消息是否仍存在(消息会持久化于erlang实现的数据库中)
        3.exclusive:
                * 是否独占(只能有一个消费者监听这个队列)
                * 当Connection关闭时,是否删除队列
        4.autoDelete:是否自动删除(当无Consumer时自动删除)
        5.arguments:其他参数
    */
    String queue = "hello_world";
    channel.queueDeclare(queue, true, false, false, null);

    // 6.接受消息
    /*
      basicConsume(String queue, boolean autoAck, Consumer callback);
      参数:
        1.queue:队列名称
        2.autoAck:是否自动确认
        3.callback:回调对象
    */
    channel.basicConsume(queue, true, new DefaultConsumer(channel) {
        /**
         * 回调方法,当收到消息后会自动执行该方法
         * @param consumerTag 标识信息(该消费者唯一标识)
         * @param envelope 更多信息(交换机、路由key等信息)
         * @param properties 配置信息
         * @param body 消息数据
         * @throws IOException
         */
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("consumerTag: " + consumerTag);
            System.out.println("exchange: " + envelope.getExchange());
            System.out.println("routingKey: " + envelope.getRoutingKey());
            System.out.println("properties: " + properties);
            System.out.println("body: " + new String(body));
        }
    });

    // 需要释放资源?NO,一旦关闭则无法监听连接与接受消息
}

三、组成部分

从上面的HelloWord例子中,我们大概也能体验到一些,就是RabbitMQ的组成,它是有这几部分:

  • Broker:消息队列服务进程。此进程包括两个部分:Exchange和Queue。
  • Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列。
  • Queue:消息队列,存储消息的队列。
  • Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。
  • Consumer:消息消费者。消费队列中存储的消息。

这些组成部分是如何协同工作的呢,大概的流程如下,请看下图:

学习笔记:分布式消息队列RabbitMQ_第9张图片

  • 消息生产者连接到RabbitMQ Broker,创建connection,开启channel。
  • 生产者声明交换机类型、名称、是否持久化等。
  • 生产者发送消息,并指定消息是否持久化等属性和routing key。
  • exchange收到消息之后,根据routing key路由到跟当前交换机绑定的相匹配的队列里面。
  • 消费者监听接收到消息之后开始业务处理。

四、工作模式

RabbitMQ 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html

学习笔记:分布式消息队列RabbitMQ_第10张图片

4.1 简单模式

学习笔记:分布式消息队列RabbitMQ_第11张图片

在上图的模型中,有以下概念:

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接收者,会一直等待消息到来
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息

4.2 工作队列模式

学习笔记:分布式消息队列RabbitMQ_第12张图片

Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。

应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

特点

  1. 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
  2. Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,
    只需要有一个节点成功发送即可。

4.3 Pub/Sub 订阅模式

学习笔记:分布式消息队列RabbitMQ_第13张图片

在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:

  • P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  • C:消费者,消息的接收者,会一直等待消息到来
  • Queue:消息队列,接收消息、缓存消息
  • Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!

特点

  1. 交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
  2. 发布订阅模式与工作队列模式的区别:
    • 工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机
    • 发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)
    • 发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑 定到默认的交换机

4.4 Routing 路由模式

队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)

  • 消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
  • Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息

学习笔记:分布式消息队列RabbitMQ_第14张图片

图解

  • P:生产者,向 Exchange 发送消息,发送消息时,会指定一个routing key
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给与 routing key 完全匹配的队列
  • C1:消费者,其所在队列指定了需要 routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要 routing key 为 info、error、warning 的消息

特点
Routing 模式要求队列在绑定交换机时要指定 routing key,消息会转发到符合 routing key 的队列

4.5 Topics 通配符模式

Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符!

  • Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
  • 通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert

学习笔记:分布式消息队列RabbitMQ_第15张图片
图解

  • 红色 Queue:绑定的是 usa.# ,因此凡是以 usa. 开头的 routing key 都会被匹配到
  • 黄色 Queue:绑定的是 #.news ,因此凡是以 .news 结尾的 routing key 都会被匹配

特点
Topic 主题模式可以实现 Pub/Sub 发布与订阅模式和 Routing 路由模式的功能,只是 Topic 在配置routing key 的时候可以使用通配符,显得更加灵活。

4.6 工作模式总结

工作模式总结:

  1. 简单模式 HelloWorld:一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。
  2. 工作队列模式 Work Queue:一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。
  3. 发布订阅模式 Publish/Subscribe:需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列。
  4. 路由模式 Routing:需要设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。
  5. 通配符模式 Topic:需要设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列。

五、高级特性

5.1 消息的可靠性投递

RabbitMQ 消息确认机制分为两大类:发送方确认、接收方确认。

其中发送方确认又分为:生产者到交换机到确认、交换机到队列的确认。

学习笔记:分布式消息队列RabbitMQ_第16张图片

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

  1. confirm 确认模式
  2. return 退回模式

rabbitmq 整个消息投递的路径为:producer ➡️ rabbitmq broker ➡️ exchange ➡️ queue ➡️ consumer

  • 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
  • 消息从 exchange 到 queue 投递失败则会返回一个 returnCallback 。

我们将利用这两个 callback 控制消息的可靠性投递

使用

设置ConnectionFactory的publisher-confirms=“true” 开启 确认模式。

  • 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后
    调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发
    送失败,需要处理。

设置ConnectionFactory的publisher-returns=“true” 开启 退回模式,设置rabbitTemplate.setMandatory(true)。

  • 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果开启了回退模式,则会将消息退回给producer。并执行回调函数returnedMessage。

在RabbitMQ中也提供了事务机制,但是性能较差,此处不做讲解。使用channel下列方法,完成事务控制:

  • txSelect():用于将当前channel设置成transaction模式
  • txCommit():用于提交事务
  • txRollback():用于回滚事务

5.2 Consumer Ack

Ack指Acknowledge确认。 表示消费端收到消息后的确认方式。有三种确认方式:

  • 自动确认:acknowledge=“none”
  • 手动确认:acknowledge=“manual”
  • 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)

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

使用

  1. 在ListenerContainer中设置acknowledge属性,设置ACK方式,none为自动确认,manual为手动确认
  2. 如果在消费端无异常发生,则调用channel.basicAck(deliveryTag.false)方法确认签收消息
  3. 如果出现异常,则在catch中调用basicNack或basicReject拒绝消息,让MQ重新发送消息

basicReject与basicNack相比较,只是前者不允许批量拒绝

5.3 Consumer 限流

Consumer可以通过配置,一次性仅拉取一定数量的消息,直到手动签收才会拉取下一条。通过此种方式达到限流的目的。

学习笔记:分布式消息队列RabbitMQ_第17张图片

使用

  1. 为消息监听者容器配置prefetchCount属性:simpleMessageListenerContainer.setPrefetchCount(2);
  2. 消费端ACK模式设置为手动签收:acknowledge=“manual”

5.4 TTL 消息存活时间

  • TTL 全称 Time To Live(存活时间/过期时间)
  • 当消息达到存活时间后还没有被消费,会被自动清除
  • RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间

学习笔记:分布式消息队列RabbitMQ_第18张图片
使用

  • 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。

  • 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断其是否过期,若过期则移除。

如果两者都进行了设置,以时间短的为准。

5.5 死信队列

死信交换机(Dead Letter Exchange)英文缩写:DLX。当消息成为死信(Dead Message)后,可以被重新发送到另一个交换机,这个交换机就是DLX。与DLX所绑定的队列就是死信队列(在其他MQ产品中也存在死信队列,但不存在交换机的概念)

学习笔记:分布式消息队列RabbitMQ_第19张图片
问题1:队列如何绑定死信交换机?

给队列设置参数:x-dead-letter-exchangex-dead-letter-routing-key

学习笔记:分布式消息队列RabbitMQ_第20张图片

问题2:消息什么时候会成为死信?

三种情况:

  1. 队列消息长度到大限制后,溢出的消息;
  2. 消费者拒收消息basicNack/basicReject。并且不把消息重新放回原目标队列requeue=false
  3. 原队列存在消息过期设置,消息到达超时时间未被消费,或者消息本身过期。

5.6 延迟队列

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

需求:

  1. 下单30min后仍未支付,提示用户支付,1hour后仍未支付,取消订单,回滚库存;
  2. 新用户注册成功7天后,发送短信问候

实现方式:

  1. 定时器
  2. 延时队列学习笔记:分布式消息队列RabbitMQ_第21张图片

但是在RabbitMQ中并未提供延时队列的实现,但我们可以使用组合TTL死信队列来实现。

学习笔记:分布式消息队列RabbitMQ_第22张图片
即给TTL队列发送消息,消息过期后会发送给DLX队列,消费者监听DLX队列。以此达到延迟效果

5.7 日志与监控

RabbitMQ默认日志存放路径:/var/log/rabbitmq/[email protected](xxx指主机名)

日志包含了RabbitMQ的版本号、Erlang的版本号、RabbitMQ服务节点名称、cookie的hash值、
RabbitMQ配置文件地址、内存限制、磁盘限制、默认账户guest的创建以及权限配置等等。

RabbitMQ命令,这些命令与控制台图形界面效果相同:
学习笔记:分布式消息队列RabbitMQ_第23张图片

5.8 消息追踪

在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。对于RabbitMQ而言,可能
是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也
有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,生产者又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失。这个时候就需要有一个较好的机制跟踪记录消息的投递过程,以此协助开发和运维人员进行问题的定位。
在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。

firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式
发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类
型的exchange。发送到这个exchange上的消息的routing key为 publish.exchangename 和
deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别
对应生产者投递到exchange的消息,和消费者从queue上获取的消息。
注意:打开 trace 会影响消息写入功能,适当打开后请关闭。

  • rabbitmqctl trace_on:开启Firehose命令
  • rabbitmqctl trace_off:关闭Firehose命令

开启消息追踪,RabbitMQ性能会有所下降


六、应用问题

6.1 消息可靠性保障

需求:100%确保消息发送成功

学习笔记:分布式消息队列RabbitMQ_第24张图片

6.2 消息幂等性保障

幂等性:一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任
意多次执行对资源本身所产生的影响均与一次执行的影响相同。在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。

使用数据库乐观锁保障:
学习笔记:分布式消息队列RabbitMQ_第25张图片

你可能感兴趣的:(RabbitMQ,分布式,学习,笔记)