Files
CategoryResourceRepost/极客时间专栏/消息队列高手课/进阶篇/22 | Kafka和RocketMQ的消息复制实现的差异点在哪?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

102 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="22 | Kafka和RocketMQ的消息复制实现的差异点在哪" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/93/9b/934bbe94d0e56802433c80b80730509b.mp3"></audio>
你好,我是李玥。
之前我在《[05 | 如何确保消息不会丢失?](https://time.geekbang.org/column/article/111488)》那节课中讲过,消息队列在收发两端,主要是依靠业务代码,配合请求确认的机制,来保证消息不会丢失的。而在服务端,一般采用持久化和复制的方式来保证不丢消息。
把消息复制到多个节点上,不仅可以解决丢消息的问题,还可以保证消息服务的高可用。即使某一个节点宕机了,还可以继续使用其他节点来收发消息。所以大部分生产系统,都会把消息队列配置成集群模式,并开启消息复制,来保证系统的高可用和数据可靠性。
这节课我们来讲一下消息复制需要解决的一些问题以及RocketMQ和Kafka都是如何应对这些问题来实现复制的。
## 消息复制面临什么问题?
我们希望消息队列最好能兼具高性能、高可用并且还能提供数据一致性的保证。虽然很多消息队列产品宣称三个特性全都支持,但你需要知道,这都是有前置条件的。
首先来说性能。任何的复制实现方式,数据的写入性能一定是不如单节点的。这个很好理解,因为无论采用哪种复制实现方式,都需要数据被写入到多个节点之后再返回,性能一定是不如只写入一个节点的。
**需要写入的节点数量越多,可用性和数据可靠性就越好,但是写入性能就越低,这是一个天然的矛盾。**不过,复制对消费的性能影响不大,不管采用哪种复制方式,消费消息的时候,都只是选择多副本中一个节点去读数据而已,这和单节点消费并没有差别。
再来说一致性,消息队列对数据一致性的要求,既包括了“不丢消息”这个要求,也包括“严格顺序”的要求。如果要确保数据一致性,必须采用“主-从”的复制方式,这个结论是有严格的数学论证的,大家只要记住就可以了。
在“主-从”模式下数据先写入到主节点上从节点只从主节点上复制数据如果出现主从数据不一致的情况必须以主节点上的数据为准。这里面需要注意一下这里面的主节点它并不是不可变的在很多的复制实现中当主节点出现问题的时候其他节点可以通过选举的方式变成主节点。只要保证在任何一个时刻集群的主节点数不能超过1个就可以确保数据一致性。
最后说一下高可用。既然必须要采用主从的复制方式,高可用需要解决的就是,当某个主节点宕机的时候,尽快再选出一个主节点来接替宕机的主节点。
比较快速的实现方式是,使用一个第三方的管理服务来管理这些节点,发现某个主节点宕机的时候,由管理服务来指定一个新的主节点。但引入管理服务会带来一系列问题,比如管理服务本身的高可用、数据一致性如何保证?
有的消息队列选择自选举的方式,由还存活的这些节点通过投票,来选出一个新的主节点,这种投票的实现方式,它的优点是没有外部依赖,可以实现自我管理。缺点就是投票的实现都比较复杂,并且选举的过程是比较慢的,几秒至几十秒都有可能,在选出新的主节点前,服务一直是不可用的。
大部分复制的实现,都不会选择把消息写入全部副本再返回确认,因为这样虽然可以保证数据一致性,但是,一旦这些副本中有任何一个副本宕机,写入就会卡死了。如果只把消息写入到一部分副本就认为写入成功并返回确认,就可以解决卡死的问题,并且性能也会比写全部副本好很多。
到底写入多少个副本算写入成功呢?这又是一个非常难抉择的问题。
假设我们的集群采用“一主二从三副本”的模式如果只要消息写入到两个副本就算是写入成功了那这三个节点最多允许宕机一个节点否则就没法提供服务了。如果说我们把要求写入的副本数量降到1只要消息写入到主节点就算成功了那三个节点中可以允许宕机两个节点系统依然可以提供服务这个可用性就更好一些。但是有可能出现一种情况主节点有一部分消息还没来得复制到任何一个从节点上主节点就宕机了这时候就会丢消息数据一致性又没有办法保证了。
以上我讲的这些内容,还没有涉及到任何复制或者选举的方法和算法,都是最朴素,最基本的原理。你可以看出,这里面是有很多天然的矛盾,所以,**目前并没有一种完美的实现方案能够兼顾高性能、高可用和一致性。**
不同的消息队列选择了不同的复制实现方式这些实现方式都有各自的优缺点在高性能、高可用和一致性方面提供的能力也是各有高低。接下来我们一起来看一下RocketMQ和Kafka分别是如何来实现复制的。
## RocketMQ如何实现复制
RocketMQ在2018年底迎来了一次重大的更新引入Deldger增加了一种全新的复制方式。我们先来说一下传统的复制方式。
在RocketMQ中复制的基本单位是Broker也就是服务端的进程。复制采用的也是主从方式通常情况下配置成一主一从也可以支持一主多从。
RocketMQ提供了两种复制方式一种是异步复制消息先发送到主节点上就返回“写入成功”然后消息再异步复制到从节点上。另外一种方式是同步双写消息同步双写到主从节点上主从都写成功才返回“写入成功”。这两种方式本质上的区别是写入多少个副本再返回“写入成功”的问题异步复制需要的副本数是1同步双写需要的副本数是2。
我刚刚讲过如果在返回“写入成功”前需要写入的副本数不够多那就会丢消息。对RocketMQ来说如果采用异步复制的方式会不会丢消息呢答案是并不会丢消息。
我来跟你说一下为什么不会丢消息。
在RocketMQ中Broker的主从关系是通过配置固定的不支持动态切换。如果主节点宕机生产者就不能再生产消息了消费者可以自动切换到从节点继续进行消费。这时候即使有一些消息没有来得及复制到从节点上这些消息依然躺在主节点的磁盘上除非是主节点的磁盘坏了否则等主节点重新恢复服务的时候这些消息依然可以继续复制到从节点上也可以继续消费不会丢消息消息的顺序也是没有问题的。
从设计上来讲,**RocketMQ的这种主从复制方式牺牲了可用性换取了比较好的性能和数据一致性。**
那RocketMQ又是如何解决可用性的问题的呢一对儿主从节点可用性不行多来几对儿主从节点不就解决了RocketMQ支持把一个主题分布到多对主从节点上去每对主从节点中承担主题中的一部分队列如果某个主节点宕机了会自动切换到其他主节点上继续发消息这样既解决了可用性的问题还可以通过水平扩容来提升Topic总体的性能。
这种复制方式在大多数场景下都可以很好的工作,但也面临一些问题。
比如,在需要保证消息严格顺序的场景下,由于在主题层面无法保证严格顺序,所以必须指定队列来发送消息,对于任何一个队列,它一定是落在一组特定的主从节点上,如果这个主节点宕机,其他的主节点是无法替代这个主节点的,否则就无法保证严格顺序。在这种复制模式下,严格顺序和高可用只能选择一个。
RocketMQ引入Dledger使用新的复制方式可以很好地解决这个问题。我们来看一下Dledger是怎么来复制的。
Dledger在写入消息的时候要求至少消息复制到半数以上的节点之后才给客户端返回写入成功并且它是支持通过选举来动态切换主节点的。
同样拿3个节点举例说明一下。当主节点宕机的时候2个从节点会通过投票选出一个新的主节点来继续提供服务相比主从的复制模式解决了可用性的问题。由于消息要至少复制到2个节点上才会返回写入成功即使主节点宕机了也至少有一个节点上的消息是和主节点一样的。Dledger在选举时总会把数据和主节点一样的从节点选为新的主节点这样就保证了数据的一致性既不会丢消息还可以保证严格顺序。
当然Dledger的复制方式也不是完美的依然存在一些不足比如选举过程中不能提供服务。最少需要3个节点才能保证数据一致性3节点时只能保证1个节点宕机时可用如果2个节点同时宕机即使还有1个节点存活也无法提供服务资源的利用率比较低。另外由于至少要复制到半数以上的节点才返回写入成功性能上也不如主从异步复制的方式快。
讲完了RocketMQ我们再来看看Kafka是怎么来实现复制的。
## Kafka是如何实现复制的
Kafka中复制的基本单位是分区。每个分区的几个副本之间构成一个小的复制集群Broker只是这些分区副本的容器所以Kafka的Broker是不分主从的。
分区的多个副本中也是采用一主多从的方式。Kafka在写入消息的时候采用的也是异步复制的方式。消息在写入到主节点之后并不会马上返回写入成功而是等待足够多的节点都复制成功后再返回。在Kafka中这个“足够多”是多少呢Kafka的设计哲学是让用户自己来决定。
Kafka为这个“足够多”创造了一个专有名词ISRIn Sync Replicas)翻译过来就是“保持数据同步的副本”。ISR的数量是可配的但需要注意的是这个ISR中是包含主节点的。
Kafka使用ZooKeeper来监控每个分区的多个节点如果发现某个分区的主节点宕机了Kafka会利用ZooKeeper来选出一个新的主节点这样解决了可用性的问题。ZooKeeper是一个分布式协调服务后面我会专门用一节课来介绍ZooKeeper。选举的时候会从所有ISR节点中来选新的主节点这样可以保证数据一致性。
默认情况下如果所有的ISR节点都宕机了分区就无法提供服务了。你也可以选择配置成让分区继续提供服务这样只要有一个节点还活着就可以提供服务代价是无法保证数据一致性会丢消息。
Kafka的这种高度可配置的复制方式优点是非常灵活你可以通过配置这些复制参数在可用性、性能和一致性这几方面做灵活的取舍缺点就是学习成本比较高。
## 总结
这节课我们主要来讲了一下消息复制需要面临的问题以及RocketMQ和Kafka都是如何应对这些问题来实现复制的。
RocketMQ提供新、老两种复制方式传统的主从模式和新的基于Dledger的复制方式。传统的主从模式性能更好但灵活性和可用性稍差而基于Dledger的复制方式在Broker故障的时候可以自动选举出新节点可用性更好性能稍差并且资源利用率更低一些。Kafka提供了基于ISR的更加灵活可配置的复制方式用户可以自行配置在可用性、性能和一致性这几方面根据系统的情况来做取舍。但是这种灵活的配置方式学习成本较高。
并没有一种完美的复制方案,可以同时能够兼顾高性能、高可用和一致性。你需要根据你实际的业务需求,先做出取舍,然后再去配置消息队列的复制方式。
## 思考题
假设我们有一个5节点的RocketMQ集群采用Dledger5副本的复制方式集群中只有一个主题50个队列均匀地分布到5个Broker上。
如果需要你来配置一套Kafka集群要求达到和这个RocketMQ集群一样的性能不考虑Kafka和RocketMQ本身的性能差异、可用性和数据一致性该如何配置欢迎在留言区与我分享讨论。
感谢阅读,如果你觉得这篇文章对你有帮助,也欢迎把它分享给你的朋友。