# 消息队列 - 如何保证消息队列的高可用

原文链接: <https://juejin.im/post/5c99e577f265da60e9268d00>

### 1. RabbitMQ的高可用性

RabbitMQ是比较有代表性的，因为是基于主从做高可用性的，我们就以他为例子讲解第一种MQ的高可用性怎么实现。

RabbitMQ有三种模式:

* 单机模式
* 普通集群模式
* 镜像集群模式

#### 1.1 单机模式

就是demo级别的，一般就是你本地启动了玩玩儿的，没人生产用单机模式

![](https://image.ldbmcs.com/2019-07-08-013412.jpg)

#### 1.2 普通集群模式

在多台机器上启动多个RabbitMQ实例，每个机器启动一个。但是你创建的Queue只会放在一个RabbitMQ实例上，但是每个实例都同步Queue的元数据。完了你消费的时候，实际上如果连接到了另外一个实例，那么那个实例会从Queue所在实例上拉去数据过来。这种方式确实很麻烦，也不怎么好，没做到所谓的分布式，就是普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据，要么固定连接那个Queue所在实例消费数据，前者有数据拉取的开销，后者导致单实例性能瓶颈。

而且如果那个放Queue的实例宕机了，会导致接下来其他实例就无法从那个实例拉取，如果你开启了消息持久化，让RabbitMQ落地存储消息的话，消息不一定会丢失，得等这个实例恢复了，然后才可以继续从这个Queue拉取数据。所以这个情况就比较尴尬，没有什么所谓的高可用性可言，这个方案主要是提高吞吐量，就是说让集群中多个节点来服务某个Queue的读写操作。

![](https://image.ldbmcs.com/2019-07-08-013600.jpg)

#### 1.3 镜像集群模式

这种模式，才是所谓的RabbitMQ的高可用模式，跟普通集群模式不一样的是，你创建的Queue，无论元数据还是Queue里的消息都会存在于多个实例上，然后每次你写消息到Queue的时候，都会自动把消息到多个实例的Queue里进行消息同步。

**这样的话，好处在于：**

* 你任何一个机器宕机了，没事儿，别的机器都可以用。

**坏处在于：**

* 这个性能开销也太大了吧，消息同步所有机器，导致网络带宽压力和消耗很重！
* 这么玩儿，就没有扩展性可言了，如果某个Queue负载很重，你加机器，新增的机器也包含了这个Queue的所有数据，并没有办法线性扩展你的Queue，那么怎么开启这个镜像集群模式呢？

这里简单说一下，避免面试人家问你你不知道，其实很简单RabbitMQ有很好的管理控制台，就是在后台新增一个**策略**，这个策略是镜像集群模式的策略，指定的时候可以要求数据同步到所有节点的，也可以要求就同步到指定数量的节点，然后你再次创建Queue的时候，应用这个策略，就会自动将数据同步到其他的节点上去了。

### 2. Kafka的高可用性

Kafka一个最基本的架构认识：多个broker组成，每个broker是一个节点；你创建一个topic，这个topic可以划分为多个partition，每个partition可以存在于不同的broker上，每个partition就放一部分数据。

这就是天然的分布式消息队列，就是说一个topic的数据，是分散放在多个机器上的，每个机器就放一部分数据。

实际上rabbitmq之类的，并不是分布式消息队列，他就是传统的消息队列，只不过提供了一些集群、HA的机制而已，因为无论怎么玩儿，RabbitMQ一个queue的数据都是放在一个节点里的，镜像集群下，也是每个节点都放这个queue的完整数据。

Kafka 0.8以前，是没有HA机制的，就是任何一个broker宕机了，那个broker上的partition就废了，没法写 也没法读，没有什么高可用性可言。

Kafka 0.8以后，提供了HA机制，就是**replica副本机制**。每个partition的数据都会同步到其他机器上，形成自己的多个replica副本。然后所有replica会选举一个leader出来，那么生产和消费都跟这个leader打交道，然后其他replica就是follower。写的时候，leader会负责把数据同步到所有follower上去，读的时候就直接读leader上数据即可。只能读写leader？很简单，要是你可以随意读写每个follower，那么就要care数据一致性的问题，系统复杂度太高，很容易出问题。Kafka会均匀的将一个partition的所有replica分布在不同的机器上，这样才可以提高容错性。

这么搞，就有所谓的高可用性了，因为如果某个broker宕机了，没事儿，那个broker上面的partition在其他机器上都有副本的，如果这上面有某个partition的leader，那么此时会重新选举一个新的leader出来，大家继续读写那个新的leader即可。这就有所谓的高可用性了。

写数据的时候，生产者就写leader，然后leader将数据落地写本地磁盘，接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了，就会发送ack给leader，leader收到所有follower的ack之后，就会返回写成功的消息给生产者。（当然，这只是其中一种模式，还可以适当调整这个行为）

消费的时候，只会从leader去读，但是只有一个消息已经被所有follower都同步成功返回ack的时候，这个消息才会被消费者读到。
