消息队列(二)消息队列的高可用原理
高可用的定义
上一篇文章提过,引入了消息队列的优势在于解耦、并发和缓冲,代价就是让程序的复杂性上升,引入了消息队列后,就需要考虑消息队列对于系统整体的影响,此时消息队列的稳定和健壮就是重中之重。也就是消息队列的高可用性。
针对消息队列,高可用性指的是确保消息队列服务在面对硬件故障、软件错误或其他可能影响其正常运行的情况时,仍能持续提供稳定可靠的服务。实现高可用性的目的是为了最小化停机时间,并保证数据不丢失或至少将损失降到最低。
RabbitMQ 的高可用性原理
RabbitMQ实现高可用的原理是基于主从(非分布式)原理。
在使用上可以分为单机模式、集群模式、镜像集群模式三种,其中单机模式只能是Demo,单节点崩溃即崩溃了,没有高可用之说;集群也只是单纯的集群,本质上也无法达到高可用,只有镜像集群可以谈高可用。下面针对集群和镜像集群进行解释。
集群模式
严格来讲,集群模式下的RabbitMQ是不具备高可用性的,因为它就是基本的集群部署,原理是这样的。
在多台机器上启动多个 RabbitMQ 实例,每台机器启动一个。程序创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据。
元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例
在消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
这种方式并没有做到分布式,这就导致消费时,要么是随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。
而且如果那个放 queue 的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果开启了消息持久化,让 RabbitMQ 落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据。
集群模式下并没有做到真正的高可用,而是集群中多个节点来服务某个 queue 的读写操作。仅仅是利用物理机器的资源提高了吞吐量。
镜像模式
RabbitMQ 可以在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去。
跟普通集群模式不一样的是,在镜像集群模式下,具体包含数据的实例节点会被镜像到不同的机器上,此时即完成了数据的“分布式”。创建一个 queue,无论是元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。
此种模式下,任何一个实例宕机了,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。但是缺点也是十分明显的:首先就是性能开销过大,消息需要同步到所有机器上,导致网络带宽压力和消耗很重;其次这是镜像,并不是真正意义的分布式,无法做到动态的横向扩展,扩展性很低。
Kafka的高可用性原理
相比RabbitMQ,Kafka则是天然的大数据高可用消息队列,首先Kafka本身就基于副本机制实现了分布式。
首先Kafka的集群是由多个broke组成,每个 broker 是一个节点。当创建一个topic时,topic会分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就存放一部分数据。每个 partition 的数据都会被拷贝,形成副本(replica)同步到其它机器上,形成自己的多个 replica 副本。副本分布在不同的broker上,Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上。保证极高的容错性。
所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。这样设计的好处在于使用时不需要考虑读写数据的一致性,一致性问题都交给leader和Kafka集群内部机制去保证。
在这种模式下,如果某个 broker 宕机了,那个 broker 上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中重新选举一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就实现了高可用。
写数据的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。
消费的时候,只会从 leader 去读,但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到。