这是【码哥】Kafka系列的第二篇文章,码哥将从原理、实践、源码的角度深入剖析和实践Kafka。本系列包括【原理】、【实践】和【源码】。这是【原理】的第二部分,主要讲解Kafka的架构和实现原理。读者可以回顾往期文章《Kafka 性能篇:为何 Kafka 这么"快"?》。今天我们将深入讲解Kafka的架构和实现原理。【码哥】将从架构和细节入手,用生动的图片深入讲解Kafka的实现原理。我想很多同学之前可能已经看过很多关于Kafka原理相关的文章,但是读起来往往听上去“很美”,充满激情。他们总觉得自己学会了各种“悬天”术。但是很多同学往往意识不到其中的犀利,背文章结合面试题还是可以应付半吊子面试官的。你可以遇到老司机面试官,也可以进入实战,但对很多概念和实现都模棱两可。因此,【码哥】决定对Kakfa进行图解,却让很多一知半解的同学加深了对Kafka实现原理的理解。同时建议读者和同学结合Kafka的配置了解Kafka的实现原理。Kafka拥有大量的配置,这也是Kafka高扩展性的体现。很多同学不敢轻易更改Kafka的配置。因此,了解这些配置背后的实现原理,可以帮助我们理解在实践中如何使用和优化Kafka。你可以在采访中或在实战中制造火箭。Kafka配置说明链接:https://kafka.apache.org/documentation以下为本文主要内容:由于内容太多,怕步骤太大,所以【码哥】决定把文章分成三个部分。本文将只处理上图中“橙色”的部分。从这篇文章中,你将了解到:Kafka架构设计理念与原则Kafka中Zookeeper的作用KafkaController实现原理Kafka网络原理开篇消息尽量做一些产品。拥有一份作品很重要,它是别人了解你的窗口。可能的话,给自己开一个公众号或者一个博客,记录下你每天的观察和思考。一开始背起来会很乱很不合逻辑,但是坚持下来肯定会有很大的价值。架构理解Kafka架构就是理解Kafka各个组件的概念以及这些组件之间的关系。让我们简要地看一下每个组件及其简要说明。不要试图记住他们Producer:生产者,发送消息的一方。生产者负责创建消息,然后将它们发送到Kafka。Consumer:消费者,接收消息的一方。消费者连接到Kafka并接收消息,然后进行相应的业务逻辑处理。消费者组:一个消费者组可以包含一个或多个消费者。采用多分区+多消费者的方式,可以大大提高数据下游的处理速度。同一个消费组中的消费者不会重复消费消息。同样,来自不同消费组的消费者的消息也不会相互影响。Kafka通过消费者组实现消息P2P模式和广播模式。Broker:服务代理节点。Broker是Kafka的服务节点,也就是Kafka的服务器。Topic:Kafka中的消息被划分为Topic单元,生产者向特定的Topic发送消息,消费者负责订阅和消费Topic消息。Partition:Topic是一个逻辑概念,可以细分为多个partition,每个partition只属于一个topic。同一主题下不同分区包含的消息是不同的。一个分区在存储层面可以看成是一个可追加的日志(Log)文件。当一条消息附加到一个分区日志文件时,一个特定的偏移量(offset)。偏移量:偏移量是消息在分区中的唯一标识。Kafka用它来保证消息在partition中的顺序,但是offset不跨越partition。也就是说,Kafka保证的是partition的顺序,而不是topic的顺序。.Replication:复制是Kafka保证数据高可用的方式。同一个KafkaPartition的数据可以在多个Brokers上有多个副本。通常,只有主副本提供读写服务。当主副本所在的broker崩溃或网络异常时,Kafka会在Controller的管理下,选出一个新的Leader副本对外提供读写服务。Record:真正写入Kafka的消息记录,可以读取。每条记录包含键、值和时间戳。当我们理解了它的时候,我们自然会记住,我们应该通过理解来记住它们。Producer-ConsumerProducer-Consumer是一种通过添加中间组件来解耦生产者和消费者的设计模式。生产者向中间组件生成数据,消费者消费数据。就像65哥读书时给小芳写情书一样,这里65哥是生产者,情书是新闻,小芳是消费者。但有时候小芳不在,或者忙,65哥也比较害羞,不敢直接把情书放在小芳手里,就把情书放在了小芳的抽屉里。所以抽屉就是这个中间组件。在程序中,我们通常使用Queue作为这个中间组件。可以使用多个线程向队列中写入数据,其他消费线程依次读取队列中的数据进行消费。模型如下图所示:生产者-消费者模型不仅可以通过增加中间层来解耦生产者和消费者,方便扩展,还可以异步调用,缓存消息等分布式队列,后来65哥和小芳搬到了不同的地方。65哥在莒都苦苦挣扎,小芳在上海逛街。所以只能通过邮局寄出模棱两可的信件。这样,65哥、邮局和小芳就变成分布式了。65哥把信送到邮局,小芳从邮局拿到65哥写的信,回去慢慢看。Kafka的消息生产者是Producer。上游消费者进程添加KafkaClient创建KafkaProducer并向Broker发送消息。Broker是部署在集群中远程服务器上的KafkaServer进程。下游消费进程引入KafkaConsumerAPI,持续消费队列中的消息。.因为KafkaConsumer采用Poll方式,Consumer需要主动拉取消息。所有小芳只能定期去邮局取信(好吧,主动权真的在小芳手里)。主题邮局不能只为65哥服务,虽然65哥一天要写好几封信。但邮局的损失是无法挽回的。所以邮局是供任何人寄信的。寄件人只需写上地址(主题),邮局在两地之间有通道收发信件。Kafka的Topic相当于一个队列,Broker是部署所有队列的机器。可以根据业务创建不同的Topic,Producer向其所属业务的Topic发送消息,相应的Consumer可以消费和处理消息。由于65哥写的信太多,一个邮局已经不能满足65哥的需要,邮政公司只能再建几个邮局。65哥把信件按照私密性(分区策略)分类,从不同的邮局寄出。可以为同一个Topic创建多个分区。理论上分区越多,并发度越高。Kafka会根据分区策略将分区尽可能均匀的分布在不同的Broker节点上,避免消息倾斜,不同Broker的负载差异过大。分区越多越好。毕竟邮政公司太多了,管不了。具体原因请参考【码哥】之前的文章《Kafka 性能篇:为何 Kafka 这么"快"?》文案是为了防止因邮局出现交通中断、邮车没油等问题。结果65哥的暧昧信发不给小芳,害得65哥夜里远程跪在键盘上。邮局决定将65哥的信复印几份,寄到多个正常的邮局,这样只要还有一个邮局,小芳就可以收到65哥的信。Kafka使用分区副本来确保数据的高可用性。每个分区都会建立指定数量的副本。Kakfa保证同一个partition的副本尽可能的分布在不同的Broker节点上,防止因为Broker宕机导致所有副本不可用。Kafka会在分区的多个副本中选出一个作为主副本(Leader),主副本对外提供读写服务,从副本(Follower)实时同步Leader的数据。很多消费者,65哥的信满天飞,小芳每天都去邮局,一封一封打开看。65哥写的信又臭又长,看得小芳满头大汗。于是小芳点了点,很快,她就变身成多个分身去不同的邮局取信,小芳终于可以腾出时间去逛街了。广播新闻邮局近期推出定制明信片服务,每个人都可以设计一张明信片,同一身份只能收到一种明信片。65哥设计了一堆,广播给所有可以来领取的美女小姐姐,美女变身后的头像也可以来领取,但是同一身份的多个头像只能领取一种明信片。Kafka通过ConsumerGroup实现广播方式的消息订阅,即不同组下的消费者可以重复消费消息,互不影响,同一组下的消费者形成一个整体。最后,我们完成了Kafka的整体架构,如下:ZookeeperZookeeper是一个成熟的分布式协调服务,可以为分布式服务提供分布式配置服务、同步服务、命名注册等能力。对于任何分布式系统,都需要一种协调任务的方法。Kafka是一个使用ZooKeeper构建的分布式系统。但也有一些其他技术(如Elasticsearch和MongoDB)有自己内置的任务协调机制。Kafka将Broker、Topic和Partition的元数据信息存储在Zookeeper上。Kafka通过在Zookeeper上建立相应的数据节点,并监控节点的变化,使用Zookeeper完成以下功能:KafkaController的Leader选举Kafka集群成员管理Topic配置管理Partition副本管理我们来看看Kafka在Zookeeper下创建的节点,即这些相关功能一目了然。ControllerController是从Broker中选出的,负责分区Leader和Follower的管理。当分区的领导副本发生故障时,控制器负责为分区选举新的领导副本。当检测到一个partition的ISR(In-SyncReplica)集合发生变化时,controller负责通知所有broker更新它们的元数据信息。当使用kafka-topics.sh脚本增加主题的分区数时,控制器还负责分区的重新分配。Kafka中Controller的选举依赖于Zookeeper。成功选择为控制器的经纪人将在Zookeeper中创建一个/控制器的临时(临时)节点。在选举过程中,Broker在启动时会尝试读取/controller节点的brokerid的值。如果brokerid的值不等于-1,说明其他Broker已经成功成为Controller节点,当前Broker主动放弃选举;如果没有/controller节点或brokerid值异常。当前Broker尝试创建节点/controller。这时,其他broker可能会同时尝试创建这个节点。只有创建成功的broker才会成为controller,创建失败的broker代表选举失败。每个broker都会在内存中保存当前controller的brokerid值,可以标识为activeControllerId。实现Controller读取Zookeeper中的节点数据,初始化上下文(ControllerContext),管理节点变更,变更上下文,还需要将这些变更信息同步到其他普通的broker节点。controller通过定时任务或者listener方式获取zookeeper信息,事件监听会更新和更新context信息。如图所示,Controller内部也是采用生产者-消费者的实现方式,Controller通过eventsQueue将zookeeper的变化发送给事件,该队列是一个LinkedBlockingQueue,事件消费者线程组将相应的事件同步到各个Broker节点通过消费和消费事件。这种队列先进先出的方式保证了消息的顺序。职责Controller被选举为整个Broker集群的管理者,管理所有的集群信息和元数据信息。其职责包括以下几个部分:处理Broker节点的上线和下线,包括自然下线、宕机、网络不可达等引起的集群变化。Controller需要及时更新集群元数据,通知所有Broker集群变化Cluster节点;创建一个topic或者topicexpansionpartition,Controller需要负责分区副本的分发,并主导topic分区副本的leader选举。管理集群中所有副本和分区的状态机,监控状态机变化事件,并做出相应的处理。Kafka分区和副本数据以状态机的形式进行管理。分区和副本的变化会引起状态机状态的变化,从而触发相应的变化事件。65哥:状态机,听起来好复杂。控制器管理集群中所有副本和分区的状态机。不要被状态机这个词所迷惑。了解状态机很简单。首先了解模型,也就是什么是什么模型,然后是模型的状态是什么,模型状态之间是如何转换的,转换的时候发送相应的change事件。Kafka的分区和副本状态机很简单。我们先了解一下,这是分别管理KafkaTopic的分区和副本。它们的状态也很简单,就是增删改查,具体如下:分区状态机PartitionStateChange,管理主题分区,有以下四种状态:创建。NewPartition:分区刚刚创建后,处于这种状态。在这个状态下,partition已经被分配了replicas,但是leader还没有选出来,也没有ISR列表。OnlinePartition:这个分区的leader一旦被选举出来,就会处于这个状态。OfflinePartition:当分区的领导者宕机时,它转移到这个状态。我们用一张图直观的看一下这些状态是如何变化的,以及当状态变化时Controller有哪些操作:副本状态机ReplicaStateChange、副本状态、管理分区副本信息。它也有4种状态:NewReplica:创建topic和partition分配后,创建replicas。此时,replicas只能获取follower的状态变化请求。OnlineReplica:当replica成为parition的assignedreplicas时,其状态变为OnlineReplica,为有效的OnlineReplica。OfflineReplica:当一个replica下线时,进入该状态,一般发生在broker宕机时;NonExistentReplica:副本删除成功后,副本进入NonExistentReplica状态。副本状态之间的变化如下图所示,当状态发生变化时Controller会做出相应的操作:NetworkKafka的网络通信模型是基于NIO的Reactor多线程模型设计的。它包含一个用于处理新连接的Acceptor线程。Acceptor有N个Processor线程选择和读取socket请求,有N个Handler线程处理请求和响应,即处理业务逻辑。下面是KafkaServer的模型图:在接下来的Kafka源码章节中,【码哥】将从源码的角度来讲解这些原理在代码中的具体实现,敬请期待。
