Kafka是一个分布式消息系统或是一个分布式流式处理平台,具有高吞吐、持久化、水平扩展、流数据处理(Spark、Storm、Flink等)等多种特性。
Kafka整体设计是典型的发布与订阅系统,且没有”中心主节点“概念,集群中所有的服务器器都是对等的,因此可以在不做任何配置修改的情况下实现服务器的添加与删除。
操作系统的页缓存
,由操作系统决定什么时候写入到磁盘。且Kafka采用的是追加写入
的方式,避免了磁盘随机写操作。一个典型的Kafka架构包括Producer(生产者)、Broker(代理)、Consumer(消费者),以及ZooKeeper集群。
其中ZooKeeper负责集群元数据管理、控制器的选举等操作。
负责生产消息,并发送到Kafka中。在发送消息的过程中,采用延迟发送,以达到积累一定数量消息,采用批发送消息。达到提供消息吞吐率。
负责消费Kafka上消息,并进行相应的业务逻辑处理。
Broker可以简单地看做一个独立的Kafka服务器节点,用于存储消息。
Kafka以主题为单位进行归类,生产者负责将消息发送到特定的主题(Topic),而消费者负责订阅主题(Topic)并消费。
一个主题可以跨越多个Broker(服务代理节点),以支持负载均,实现更好的性能。
主题是一个逻辑概念,它可以细分为多个分区,一个分区只属于单个主题。
同一主题下的不同分区包含的消息是不同的,分区在存储层面可以看做一个可追加的日志文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量
(Offset)。
每一条消息被发送到broker之前,会根据分区规则选择存储到哪个具体的分区。如果分区设置的合理,所有的消息可以均匀地分布在不同的分区中。
如果一个主题只有对应一个文件,那么这个文件所在机器I/O将会成为这个主题的性能瓶颈。
通过多分区可以解决主题单分区的性能瓶颈,可以在创建主题的时候指定分区,也可以在创建完成之后修改分区数量,通过增加分区数量可以实现水平扩展。
通过副本数量提升容灾能力,同一分区的不同副本保存的是相同数据(在同一时刻,副本之间信息并不完全一样),副本之间是”一主多从“的关系。
其中Leader副本复制处理读写请求,follower副本只负责与leader副本的消息同步,当leader出现故障,从follower副本中重新选择一个新的leader副本对外提供服务。
Kafka通过多副本机制实现了故障自动转移,当集群中某个leader失效时然能保证服务可用。
分区中的所有副本统称为AR(Assigned Replicas)。所有与Leader副本保持一定程度同步的副本(包含Leader副本在内)组成ISR(In-Sync Replicas),ISR集合是AR集合中的一个子集。
消息先会发送到Leader副本,返回Follower副本才能从Leader副本中拉取消息进行同步,同步期间内follower副本相对于Leader副本而言有一定程度的滞后。在一定程度滞后范围内的称为ISR、其他的称为OSR。
Leader副本会跟踪ISR和OSR两个集合中的副本,当ISR中有副本落后太多就会从ISR集合中剔除,放入OSR中。而OSR集合中有副本”追上"机会从OSR集合中移除加入ISR集合中。
Leader副本发送故障,一般只能从ISR集合中选举Leader副本,OSR一般没有机会(可通过配置修改).
HW(High Watermark、高水位)
:它标识了一个特定的消息offset(偏移量),消费者只能拉取到这个offset。
LEO(Log End Offset、日志结束偏移量)
:它标识当前日志文件下一条待写入新消息的offfset。LEO的大小相当于当前日志分区最后一条消息的offset值加1.分区ISR集合中的每个分区都会维护自身的LEO,而ISR集合中最小LEO即为分区HW
,对消费者而言只能消费HW之前的消息。
用于归类消费同一主题的消费者,在Kafka中,多个消费者消费可以共同消费一个主题(Topic)下的消息,每个消费者消费其中部分消息,这些消费者就组成了一个分区,拥有同一个分组名称,通常也被称为消费者集群。
offset是消息在分区中的唯一标识,Kafka通过它保证消息在分区的顺序,Offset不能跨分区。即Kafka保证是分区有序而不是主题有序。
消费者者拉取消息数据的过程中需要知道消息在文件中的偏移量。
Kafka通过ZooKeeper来管理元数据,以及实现Kafka的高可用,以及负载均衡。
Broker是分布式部署并且相互之间是独立运行的,所以需要一个注册系统能够将真个集群中的Broker服务器都管理起来。Kafka选择了ZooKeeper进行管理所有Broker。
在ZooKeeper上会创建节点路径为/broker/ids来专门记录Broker服务器列表。每个Broker服务器在启动时,都会到ZooKeeper上进行注册,创建属于自己的临时节点,其节点路径为/broker/ids/[0…N]。
Kafka使用了一个全局唯一的数字来指定每个Broker服务器,可以称其为”Broker ID“,不同Brokder必须使用不同的Broker ID进行注册。
通过临时节点的特性,一旦这个Broker服务器宕机或下线,对应的节点会自动删除。因此可以通过ZooKeeper上Broker节点的变化情况来动态表征Broker服务器的可用性。
在Kafka中,会将同一个主题(Topic)的消息分成多个分区将其分布到多个Broker上,而这些分区消息以及与Broker的对应关系也需要由ZooKeeper维护,有专门的”Topic 节点“节点来记录,其节点路径为/brokers/topics。
Kafka中的每个主题(Topic),都会以/broker/topics/[topic]的形式记录在这个节点下,如:/broker/topics/login。
Broker服务器在启动后,会到对应的Topic节点下注册自己的Broker ID,并写入针对该Topic的分区总数。
例如:/broker/topics/login/3 -> 2这个节点表名Broker服务器的 Broker ID为3,对于”login“这个主题(Topic)的消息,提供了2个分区进行消息存储。同时这个节点也是临时节点。
Kafka是分布式部署Broker服务器的,会对同一个Topic的消息进行分区并将其分布到不同的Broker服务器上。因此生产者需要将消息合理地发送到这些分布式的Broker上----这就面临一个问题:如和进行生产者的负载均衡。
Kafka支持传统的四层负载均衡,也同时支持使用ZooKeeper方式实现负载均衡。
根据生成者的IP地址和端口来为其确定一个相关的Broker。通常一个生产者只会对应单个Broker,然后所有消息都发送给这个Broker。
优点:整体逻辑简单,不需要引入其他三方系统,同时每个生成者也不需要同其他系统建立额外的TCP链接,只需要和Broker维护单个TCP链接即可。
缺点:无法做到真正的负载均衡。在实现运行环境中,每个生成者产生的消息量,以及每个Broker的消息存储量都是不一样的,如果有些生产者产生消息远多于其他生产者,那么会导致不同的Broker接受到的消息总数非常不均匀。
另一方面,生产者也无法实时感知到Broker的新增与删除,因此,这种负载均衡方式无法做到动态的负载均衡。
在Kafka中,客户端使用了基于ZooKeeper的负载均衡策略来解决生产者的负载均衡问题。
每当一个Broker启动时,会首先完成Broker注册过程,并注册一些诸如”有哪些可订阅的Topic“的元是数据信息。生产者就能够通过这个节点变化来动态地感知到Broker服务器列表的变更。
Kafka的生产者会对ZooKeeper上的”Broker的新增与减少“、”Topic的新增与减少“和“Broker与Topic关联的变化”等事件注册Watcher监听,这样就可以实现动态的负载均衡机制。
这种模式下,还可以允许开发人员控制生产者根据一定的规则(例如根据消费者的消费行为)来进行分区,而不仅仅是随机算法而已------Kafka将这种特定的分区策略称为“语义分区”。
通过ZooKeeper和Watcher通知能够让生产者动态获取Broker和Topic的变化请情况。
Kafka中的消费者需要进行负载均衡来实现多个消费者合理地从对应的Broker服务器上接收消息。Kafka有消息分组的概念,每个消费者分组中包含若干个消费者,每一条消息都只会发送给分组中一个消费者
,不同的消费者分组消息自己特定主题(Topic)下面的消息
,互不干扰。
对于每个消费者分区,Kafka都会为其分配一个全局唯一的Group ID,同一个消费者分组内部的所有消息都共享该ID,同时,Kafka也会为每个消息者分配一个Consumer(消费者) ID,通常采用“Hostname:UUID”的形式表达。
Kafka的设计中,规范了每个消息分区有且只能同时一个消费者进行消息
的消费。因此,需要在ZooKeeper上记录消息分区与消息者之间的关系。每个消息者一但确定了一个消息分区的消费权利,那么将其Consumer(消费者) ID写入到对应消息分区的临时节点上。
例如:/consumer/[group_id]/owners/[topic]/[broker_id-partition_id],其中“[broker_id-partition_id]”就是一个消息分区的标识,节点内容就是消费该分区的消费者Customer ID。
消息者对指定消息分区进行消息消费的过程中,需要定时地将分区消息的消费进度。即Offset记录到ZooKeeper上去,以便在改消费者宕机或是其他消息者重新接管该消息分区的消息消费后,能够从之间的进度开始继续进行消息的消费。
Offset在ZooKeeper上的记录有一个专门的节点负责,其节点路径为/customer/[group_id]/offsets/[topic]/[broker_id-partition_id],其节点内容就是Offset值。
消费者服务器在初始化穷时加入消费者分组的过程。
Kafka借助ZooKeeper上记录的Broker和消费者信息,采用了一套特殊的消费者负载均衡算法。
将一个消费者分组的每个消费者记录为C1,C2—CG,那么对于一个消费者Ci,其对应消息分区的分配策略如下:
本文章大部分内容来自《深入理解Kafak:核心设计与实践原理》