当前位置: 首页 > 科技观察

《吃透 MQ 系列》的Kafka架构设计任杜二麦

时间:2023-03-15 22:35:37 科技观察

大家好,我是吴哥。这是《吃透 MQ 系列》的第三篇文章,关于Kafka的架构设计。本文将带你了解:什么是Kafka架构设计的二脉?掌握了这个重点之后,相信你就能更好的理解Kafka的架构设计,进而掌握Kafka方案的核心技术。废话不多说,我们上车吧。1、Kafka的技术难点是什么?上一篇《扒开 Kafka 的神秘面纱》解释了两个关键信息:1.Kafka为实时日志流而生,并发量和处理的数据量都非常大。由此可见,Kafka本身就是一个高并发系统,在高并发场景下必然会遇到典型的三高挑战:高性能、高可用、高扩展性。2、为了简化实现复杂度,Kafka最终采用了一个非常巧妙的消息模型:它持久化存储所有的消息,让消费者随时随地获取他们需要的,想要的消息。你只需要传递消息的偏移量就可以拉取它。最后Kafka自己退化成了一个“存储系统”。因此,海量消息的存储是Kafka架构设计中最大的技术难点。2、任渡对Kafka架构设计的两条线我们继续分析:Kafka是如何解决存储问题的?面对海量数据,单机的存储容量和读写性能必然受到限制。大家很容易想到一种存储方案:数据的分片存储。这种解决方案在我们实际工作中也很常见:1、比如在数据库设计中,当单表的数据量达到几千万或者上亿的时候,我们就会拆分成多个库或者多个表。2、比如在缓存设计上,当单个Redis实例的数据量达到几十G,造成性能瓶颈时,我们将单机架构改为分片集群架构。类似的拆分思路在HDFS、ElasticSearch等中间件中也能看到。Kafka也不例外,它也采用了这种水平拆分方案。在Kafka的术语中,拆分后的数据子集称为Partition(分区),每个分区的数据集合就是全量数据。我们来看看Kafka中的Partition是如何工作的。举个很形象的例子,如果我们把“卡夫卡”比作“高速公路”:1、一听到京广高速就知道是一条路北京到广州的高速公路是个顺理成章的名字,可以理解作为Kafka中的主题。2.一条高速公路通常有多条车道分流,每条车道上的车都通往一个目的地(属于同一个Topic)。这里说的lane就是Partition。这样,一条消息的流向如下图所示。首先是topic路由,然后是partition路由,最终确定消息应该发送到哪个partition。其中分区路由可以简单理解为一个Hash函数,生产者可以完全自定义这个函数来确定发送消息时的分区规则。如果分区规则设置得当,所有的消息都会被平均分配到不同的分区中。通过这样的两层关系,最终在Topic下,多了一个新的划分单元:Partition。先通过Topic对消息进行逻辑分类,再通过Partition进一步进行物理分片。最后,多个Partition会均匀分布在集群中的每台机器上,从而很好地解决了存储扩展性问题。因此,Partition是Kafka最基本的部署单元。本文之所以将Partition称为Kafka架构设计的第二个分支,是基于以下两个原因:1、Partition是存储的关键,MQ的核心流程“一发一存一消费”必须围绕它发展。2.Kafka高并发设计中最难解决的三高问题,可以和Partition联系起来。因此,以Partition为根,可以很自然地将Kafka架构设计中的各个知识点联系起来,形成一个可靠的知识体系。接下来请继续按照我的思路,以Partition为线索,分析一下Kafka的宏观架构。3.Kafka的宏架构设计接下来我们看一下Partition的分布式能力是如何实现的?它与Kafka的整体架构有什么关系?前面提到Partition是Topic下的一个划分单元,是Kafka最基本的部署单元,它将决定Kafka集群的组织方式。假设有两个Topic,每个Topic有两个Partition,如果Kafka集群由两台机器组成,部署架构会是这样:可以看到同一个Topic的两个Partition分布在不同的消息服务器上其中,可以实现消息的分布式存储。但是对于Kafka这种高并发的系统来说,只有可扩展的存储是不够的,消息的拉取也必须是并行的,否则会遇到巨大的性能瓶颈。那么我们再来看一下消费者端。它是如何与Partition结合起来实现并行处理的呢?从消费者的角度来看,首先要满足两个基本需求:1.广播消费能力:同一个Topic可以被多个Subscriber消费,一条消息可以被多次消费。2、集群消费能力:当消费者本身也是一个集群时,每条消息只能分发给集群中的一个消费者进行处理。为了满足这两个需求,Kafka引入了消费者群体的概念。每个消费者都有一个对应的消费组,组间广播消费,组内集群消费。另外,Kafka也做了限制:每个Partition只能被消费组中的一个消费者消费。最终的消费关系如下图所示:假设主题A有4个分区,消费组2只有两个消费者。最终这两个消费组会平分整个负载,各自消费两个partition的消息。如果想加快消息的处理速度,该怎么做呢?也很简单,只需要在consumergroup2中增加一个新的consumer,Kafka会以Partition为单位重新进行负载均衡。当增加到4个consumer时,每个consumer只需要处理1个Partition,处理速度会翻倍。至此,可伸缩存储和消息并行处理两个问题已经解决。但是在高并发架构的设计中,还有一个很重要的问题:那就是高可用的设计。在Kafka集群中,每台机器都存储了一些Partition。某台机器一旦宕机,上面的数据不就全部丢失了吗?此时,你肯定会想到消息的持久化存储,但是持久化只能解决一部分问题,它只能保证机器重启后历史数据不会丢失。但在机器恢复之前,这部分数据将无法访问。这对于高并发系统来说是难以忍受的。因此,Kafka必须具备故障转移能力,当一台机器宕机时,仍能保证服务可用。如果分析任何一个高可靠的分布式系统,比如ElasticSearch、RedisCluster,它们其实都有多副本冗余机制。没错,Kafka通过Partition的多副本机制解决了高可用的问题。在Kafka集群中,每个Partition都有多个副本,相同的消息存放在同一个分区的不同副本中。副本之间的关系是“一主多从”。Leader副本负责读写请求,Follower副本只负责与Leader副本同步消息。当leader副本出现故障时,有机会被选举为新的leader副本并对外提供服务,否则一直处于待命状态。现在,我假设Kafka集群中有4台服务器,topicA和topicB有两个Partition,每个Partition有两个副本,那么最终的多副本架构就会如下图所示:显然,这个集群任何一台机器宕机都不会影响Kafka的可用性,数据依然完好无损。了解了以上内容,最后我们倒过来看一下Kafka的整体架构:1.Producer:生产者负责创建消息,然后将消息投递到Kafka集群。投递时需要指定消息所属的topic,同时确定目的地WhichPartition。2、Consumer:Consumer会根据自己订阅的Topic和所属的consumergroup来决定从哪些Partition中拉取消息。3、Broker:消息服务器,可横向扩展,负责分区管理、消息持久化、自动故障转移等。4、Zookeeper:负责集群元数据管理等功能,如集群中有哪些broker节点和topic,以及每个主题中有哪些分区。显然,在Kafka的整体架构中,Partition是发送、存储、消费消息的纽带。理解透彻之后,再理解整体结构,脉络就会更清晰。4.写在最后本文以Partition为切入点,从宏观的角度分析了Kafka的整体架构,并简要总结了本文的内容:1.Kafka通过巧妙的模型将自身退化为海量消息的存储系统设计。2、Kafka为了解决存储的可伸缩性问题,将数据进行了水平拆分,导致了Partition(分区),它是Kafka部署的基本单位,也是Kafka并发处理的最小粒度。3.对于高并发的系统,同样需要高可用。Kafka通过Partition的多副本冗余机制进行故障转移,保证高可靠性。希望这篇文章能帮助大家摆脱死记硬背的模式,先找到一个支点,再琢磨Kafka架构设计的来龙去脉,知其所以然。本文转载自微信公众号《五哥谈IT》,可通过以下二维码关注。转载本文请联系五哥谈IT公众号。