RabbitMQ学习(二):高可靠之持久化与高可用之镜像队列

一、概述

1. 分布式系统的需要

  • RabbitMQ 是对内存队列,如 Java 的阻塞队列 BlockingQueue,的一种升级,即作为一个进程队列实现不同进程之间的消息通信交互,而内存队列,如 BlockingQueue 则通常用于实现一个 Java 进程的不同线程之间的消息通信交互。这也是顺应从单体应用到分布式系统的演变所必须的消息队列的演进,解决了分布式系统不同系统之间的消息传递问题。

2. 基于内存存储

  • 由于 RabbitMQ 主要用于实现不同进程或者说不同系统之间的消息传递,与内存队列在进程重启自动销毁类似,RabbitMQ 的相关组件,如交换器,队列,消息等默认情况下也是存放在内存中的,当 RabbitMQ 服务器重启时,这些组件相关的元数据会丢失,重启时需要重新创建这些核心组件。
  • 这样设计的合理之处在于 RabbitMQ 并不是一个数据存储系统,而是相对于内存队列,提供了通过网络的方式为不同系统进行消息传递,这也是 AMQP 协议的核心所在。

3. 数据持久化

  • 不过在实际应用中,由于组成分布式系统的各个子系统的功能通常是固定的,如电商网站中的账号系统,订单系统,物流系统等,当选择 RabbitMQ 作为这些子系统的消息队列时,通常需要保证 RabbitMQ 在意外宕机或者重启后,相关的交换器,队列,未消费的消息还存在,而不需要重新创建交换器或者队列,并且未消费的消息不会丢失,这样才能减少对生产者和消费者,如以上的电商系统的相关业务子系统,的影响。
  • 所以需要对交换器,队列和队列的消息进行持久化处理,即持久化到磁盘中,当 RabbitMQ 宕机或者重启时,可以从磁盘将这些组件的数据重新加载到内存中。

二、持久化:高可靠

1. 元数据持久化

  • 交换器和队列的持久化主要是在创建交换器或者队列时,通过设置durable 参数为 true 来告诉 RabbitMQ 对这个所创建的交换器或者队列进行持久化处理。
  • 通常需要对交换器和队列同时设置持久化,这样能保证不只是交换器和队列在 RabbitMQ 重启时还存在,交换器和队列之间的绑定关系 Binding 也还存在,这样对生产者和消费者影响最小,即生产者和消费者可以继续正常生产数据或者消费数据,就像 RabbitMQ 没有重启过一样。
  • 交换器和队列的持久化主要是针对交换器和队列的相关元数据的持久化,将这些元数据持久化到磁盘中。
    1. 对于交换器而言,需要持久化的元数据主要包括队列的名字,队列类型,如 faout,direct,topic或者headers,以及其他相关属性,如持久化属性durable。交换器持久化之后,当RabbitMQ重启后,生产者可以继续使用这个交换器来传递数据,而无需在手动创建这个交换器,这样能尽可能地降低对生产者的影响。
    2. 对于队列而言,需要持久化的元数据主要为队列的名字,以及属性,如是否持久化 durable,是否自动删除。不过这里需要注意的是,如果一个队列是排他队列,即只对当前的连接 Connection 有效,只能被当前连接Connection 的多个 Channel 访问,则即使设置了该队列为持久队列,当这个连接 Connection 断开时,该队列也会自动删除掉,而不会再持久化到磁盘中。
    3. 除了需要持久化交换器和队列自身的元数据外,交换器和队列之间的绑定映射关系也会持久化。
    4. 在磁盘存储方面,交换器和队列的持久化信息通常存放在 rabbitmq 服务器的 /var/lib/rabbitmq/mnesia/ 目录下面。

2. 消息持久化

  • 以上的分析的交换器和队列的持久化只是针对交换器和队列自身元数据的持久化,而不会对消息进行持久化,即消息还是存放在 RabbitMQ 服务器的内存中的,如果 RabbitMQ 服务器宕机或者重启,队列中的消息会丢失。
  • 所以如果要保证投递到队列中的消息在 RabbitMQ 服务器宕机重启时不会丢失,需要进行消息持久化。消息持久化主要是在生产者控制的。即生产者在创建消息时,可以设置消息的属性 properties 的 deliveryMode 为 2 来指定这个消息是需要持久化的。
  • 当将该消息投递到 RabbitMQ 服务器的队列之后,需要首先写到磁盘中,然后再在内存队列保留这条消息,最后如果生产者需要确认 ACK,则再回调通知生产者这条消息投递成功。
  • 由于消息写入磁盘是随机写操作,性能较低,会一定程度影响 RabbitMQ 服务器整体的吞吐量,所以一般对重要,对消息丢失容忍度低的场景的消息,才将该消息设置为持久化。还有就是当对磁盘文件写入时,操作系统不是对每次写入都直接写到磁盘文件的,而是会写到操作系统的页缓存中,等之后再刷到磁盘,所以如果在这期间,机器宕机了,则即使设置了消息持久化,也可能造成消息丢失。

三、镜像队列:高可用

  • 以上的消息持久化机制的可靠性的前提是:该机器不宕机,磁盘不损坏,即还是存在单点故障问题,并没有实现消息在其他机器的冗余存储来避免单点故障。
  • RabbitMQ 的消息默认是只在其所被投递到的某个机器的 RabbitMQ 服务器的某个队列中存放一份,在其他队列或者其他机器的队列并没有一份拷贝,所以缺乏高可用特性。
  • 所以为了实现高可用,RabbitMQ 提供了镜像队列机制。所谓镜像队列其实就是在另外一个 RabbitMQ 服务器 Broker 存放一个该队列的一个拷贝队列,实现队列内消息的冗余存储。

主备模式

  • 镜像队列机制是基于主备模式的:
  1. 针对这个队列的所有操作都是只能在 Master 节点进行操作的,包括生产者发布消息到队列,分发消息给消费者,跟踪消费者的消费确认ACK等,然后将这些操作对应的消息由 Master 节点广播同步给其他节点。
  2. 针对消息消费,与 MySQL 和 Redis 的主从模式中 master 负责写请求,slave 负责读请求实现读写分离不同的是,在 RabbitMQ 的镜像队列模式中,消费者是从 Master 节点消费数据的,即不管消费者连接的是哪个节点进行消费,如连接到从节点消费,从节点会将消费请求转发到 master 进行消费。当成功消费并确认 ACK 删除该消息时,由 Master 节点同步这个删除信息给其他 Slave 节点。
  3. 所以镜像队列解决的是消息高可用问题,而不是基于负载均衡实现的高吞吐量问题。高吞吐量主要是通过 RabbitMQ 集群来实现的,即在不同节点创建不同队列来提高消息处理能力和吞吐量。
  4. 消费者默认不会从 Slave 节点消费数据,只有当 Master 节点宕机,Slave 节点升级为 Master 节点时才会消费这个节点的数据。Master 节点宕机时,会从所有 slave 节点中选择最早加入这个镜像队列集合的 slave 作为新的master,即根据加入时间来判断的。

参考:
RabbitMQ官方文档:Highly Available (Mirrored) Queues

你可能感兴趣的:(中间件)