vlambda博客
学习文章列表

消息队列实现复制的最佳实践

对于MQ

  • 在Pro、Con客户端,依靠业务代码,配合请求确认机制保证不会丢消息。

  • 在服务端,一般采用持久化和复制做保证。

把消息复制到多个节点,不仅可解决丢消息问题,还可保证消息服务的HA。所以都会把MQ配置集群模式,并开启消息复制保证系统。

那么消息复制需要解决哪些问题呢?

消息队列实现复制的最佳实践

1 消息复制的指标


我们希望MQ兼具高性能、高可用并且还能提供数据一致性。虽然很多MQ宣称三个特性全都支持,但这都是有前置条件的。

1.1 性能

首先明确数据的写性能一定不如单节点。因为无论采用哪种复制,都需数据被写到多节点后再返回,性能一定不如只写入一个节点。

需要写入节点越多,可用性和数据可靠性越好,但写性能就越低。
不过,复制对消费的性能影响不大,不管采用哪种复制方式,消费消息的时候,都只是选择多副本中一个节点去读数据,和单节点消费无异。

1.2 一致性

MQ对数据一致性要求,既包括“不丢消息”,也包括“严格顺序”。
若要确保数据一致性,必须采用主从复制。

这个结论有严格数学论证。

主从模式下,数据先写到主节点,从节点只从主节点上复制,若出现主从数据不一致,须以主节点数据为准。

这里的主节点并非不可变,在很多复制实现中,当主节点出现问题,其他节点可通过选举,变成主节点。只要保证,在任一时刻,集群的主节点数不能超过1个,就可确保数据一致性。

1.3 高可用

须采用主从复制,高可用需解决的就是,当某个主节点宕机,尽快再选个主节点来继位。

比较快速的实现方式是,使用一个第三方服务来管理这些节点,发现某主节点宕机,由管理服务指定一个新的主节点。
但引入管理服务会带来一系列问题,比如管理服务本身的高可用、数据一致性如何保证?就如 redis 哨兵机制。

有的MQ选择自选举,由还存活的这些节点通过投票,来选个新主节点。

  • 优点
    没有外部依赖,可以实现自我管理

  • 缺点
    投票的实现都比较复杂,并且选举过程是比较慢的,几秒至几十秒都可能,在选出新的主节点前,服务一直不可用。

大部分复制的实现,都不会选择把消息写入全部副本再返回确认,因为这样虽然可以保证数据一致性,但是,一旦这些副本中有任何一个副本宕机,写入就会卡死了。如果只把消息写入到一部分副本就认为写入成功并返回确认,就可以解决卡死的问题,并且性能也会比写全部副本好很多。

到底写入多少个副本算写入成功呢?这又是一个非常难抉择的问题。

假设集群采用“一主二从三副本”的模式,如果只要消息写入到两个副本就算是写入成功了,那这三个节点最多允许宕机一个节点,否则就没法提供服务了。如果说我们把要求写入的副本数量降到1,只要消息写入到主节点就算成功了,那三个节点中,可以允许宕机两个节点,系统依然可以提供服务,这个可用性就更好一些。但是,有可能出现一种情况:主节点有一部分消息还没来得复制到任何一个从节点上,主节点就宕机了,这时候就会丢消息,数据一致性又没有办法保证了。

以上还没涉及任何复制或者选举的方法和算法,都是最基本的分布式原理。里面是有很多固有矛盾,所以,并没有一种完美实现方案能够兼顾高性能、高可用和一致性。

不同MQ选择了不同的复制实现方式,有各自的优缺点,在高性能、高可用和一致性方面提供的能力也是各有高低。
接下来我们对比RocketMQ和Kafka实现复制。

消息队列实现复制的最佳实践

2 RocketMQ复制


2.1 传统复制

在RocketMQ中,复制的基本单位是Broker,服务端进程。采用主从复制,通常配置成一主一从,也支持一主多从。

RocketMQ提供两种

复制方式

异步复制

消息先发送到主节点,就返回“写入成功”,然后消息再异步复制到从节点。

同步双写

消息同步双写到主从节点,主从都写成功,才返回“写入成功”。
这两种方式本质上的区别是,写入多少个副本再返回“写入成功”的问题,异步复制需要的副本数是1,同步双写需要的副本数是2。

如果在返回“写入成功”前,需要写入的副本数不够多,那就会丢消息。
对RocketMQ来说,如果采用异步复制的方式会不会丢消息呢?不会丢。

为什么不会丢消息

RocketMQ的Broker的主从关系是通过配置固定,不支持动态切换。
如果主节点宕机,生产者就不能再生产消息,消费者可自动切换到从节点继续进行消费。
这时候,即使有一些消息没有来得及复制到从节点上,这些消息依然躺在主节点的磁盘,除非是主节点的磁盘坏了,否则等主节点重新恢复服务的时候,这些消息依然可以继续复制到从节点上,也可以继续消费,不会丢消息,消息顺序也没有问题。

这种主从复制方式,牺牲可用性,得到较好性能和数据一致性。

可用性

一对主从节点可用性不行,那就多对。

  • 功能
    RocketMQ支持把一个主题分布到多对主从节点,每对主从节点中承担主题中的一部分队列。

  • 表现
    若某主节点宕机,自动切换到其他主节点继续发消息。

  • 解决如下问题:

    • 可用性

    • 还可通过水平扩容提升Topic性能

旧复制缺陷

由于topic层无法保证严格顺序,必须指定队列发消息,对任一队列,一定是落在一组特定主从节点,若该主节点宕机,其他主节点无法替代这主节点,否则就无法保证严格顺序。
因此这种复制模式的严格顺序和高可用只能选其一。

2.2 新复制

2018年底引入Deldger,一种全新复制方式。

Dledger在写入消息时,要求至少消息复制到半数以上节点后,才给客户端返回写成功,且支持选举动态切换主节点。

执行原理

3节点为例。当主节点宕机,2个从节点会通过投票选出一个新主节点,相比主从复制,解决了可用性问题。
由于消息要至少复制到2个节点上才返回写成功,即使主节点宕机,也至少有一节点消息是和主节点一致。选举时,总会把数据和主节点一样的从节点选为新主,保证了数据一致性,既不会丢消息,还可保证严格顺序。

新复制的缺陷

选举过程中不能提供服务。最少需要3节点才能保证数据一致性。
3节点时,只能保证1个节点宕机时可用,如果2个节点同时宕机,即使还有1个节点存活也无法提供服务,资源利用率较低。
由于至少要复制到半数以上的节点才返回写入成功,不如主从异步复制快。

消息队列实现复制的最佳实践

3 Kafka 复制


复制的基本单位是分区。每个分区的几个副本间,构成一个小的复制集群。
Broker只是这些分区副本的容器,所以Kafka的Broker是不分主从的。

分区的多个副本中采用一主多从。
写入消息时,采用异步复制。消息在写到主节点后,并不会马上返回写入成功,而是等待足够多的节点都复制成功后再返回。这个“足够多”由用户定。对应:ISR(In Sync Replicas),“保持数据同步的副本”。ISR的数量可配,ISR中包含主节点。

Kafka使用ZooKeeper监控每个分区的多个节点,如果发现某个分区主节点宕机:

  • Kafka会利用ZooKeeper选个新主节点,这解决可用性

  • 选举时,会从所有ISR节点选新主节点,这保证数据一致性

默认如果所有ISR宕机,分区就无法提供服务。也可以选择配置成让分区继续提供服务,这样只要有一个节点活,就可提供服务,代价是无法保证数据一致性,会丢消息。

Kafka的这种高度可配置的复制方式

  • 优点
    非常灵活,可自定义配置这些复制参数,在可用性、性能和一致性这几方面做业务取舍

  • 缺点
    学习成本较高

消息队列实现复制的最佳实践

4 总结


没有完美的复制方案,可同时兼顾高性能、高可用和一致性。要根据业务需求,做出取舍,再去配置。

  • 参考

    • https://blog.51cto.com/14799494/2507807

点个在看支持我吧,转发就更好了