Kafka架构及基本原理简析
Kafka简介
Kafka是⼀个由Scala和Java编写的企业级的消息发布和订阅系统,最早是由Linkedin公司开发,最终开源到Apache软件基⾦会的项⽬。Kafka是⼀个分布式的,⽀持分区的,多副本的和多订阅者的⾼吞吐量的消息系统,被⼴泛应⽤在应⽤解耦、异步处理、限流削峰和消息驱动等场景。本⽂将针对Kafka的架构和相关组件进⾏简单的介绍。在介绍Kafka的架构之前,我们先了解⼀下Kafk的核⼼概念。
Kafka核⼼概念
在详细介绍Kafka的架构和基本组件之前,需要先了解⼀下Kafka的⼀些核⼼概念。
Producer:消息的⽣产者,负责往Kafka集中发送消息;
Consumer:消息的消费者,主动从Kafka集中拉取消息。
Consumer Group:每个Consumer属于⼀个特定的Consumer Group,新建Consumer的时候需要指定对应的Consumer Group ID。Broker:Kafka集中的服务实例,也称之为节点,每个Kafka集包含⼀个或者多个Broker(⼀个Broker就是⼀个服务器或节点)。Message:通过Kafka集进⾏传递的对象实体,存储需要传送的信息。
Topic:消息的类别,主要⽤于对消息进⾏逻辑上的区分,每条发送到Kafka集的消息都需要有⼀个指定的Topic,消费者根据Topic对指定的消息进⾏消费。
Partition:消息的分区,Partition是⼀个物理上的概念,相当于⼀个⽂件夹,Kafka会为每个topic的每个分区创建⼀个⽂件夹,⼀个Topic的消息会存储在⼀个或者多个Partition中。
Segment:⼀个partition当中存在多个segment⽂件段(分段存储),每个Segment分为两部分,.log⽂件和 .index ⽂件,其中 .index ⽂件是索引⽂件,主要⽤于快速查询.log ⽂件当中数据的偏移量位置;
.log⽂件:存放Message的数据⽂件,在Kafka中把数据⽂件就叫做⽇志⽂件。⼀个分区下⾯默认有n多个.log⽂件(分段存储)。⼀个.log⽂件⼤默认1G,消息会不断追加在.log⽂件中,当.log⽂件的⼤⼩超过1G的时候,会⾃动新建⼀个新的.log⽂件。
kafka最新版本.index⽂件:存放.log⽂件的索引数据,每个.index⽂件有⼀个对应同名的.log⽂件。
后⾯我们会对上⾯的⼀些核⼼概念进⾏更深⼊的介绍。在介绍完Kafka的核⼼概念之后,我们来看⼀下Kafka的对外提供的基本功能,组件及架构设计。
Kafka API
如上图所⽰,Kafka主要包含四个主要的API组件:
1. Producer API
应⽤程序通过Producer API向Kafka集发送⼀个或多个Topic的消息。
2. Consumer API
应⽤程序通过Consumer API,向Kafka集订阅⼀个或多个Topic的消息,并处理这些Topic下接收到的消息。
3. Streams API
应⽤程序通过使⽤Streams API充当流处理器(Stream Processor),从⼀个或者多个Topic获取输⼊流,并⽣产⼀个输出流到⼀个或者多个Topic,能够有效地将输⼊流进⾏转变后变成输出流输出到Kafka集。
4. Connect API
允许应⽤程序通过Connect API构建和运⾏可重⽤的⽣产者或者消费者,能够把kafka主题连接到现有的
应⽤程序或数据系统。Connect实际上就做了两件事情:使⽤Source Connector从数据源(如:DB)中读取数据写⼊到Topic中,然后再通过Sink Connector读取Topic中的数据输出到另⼀端(如:DB),以实现消息数据在外部存储和Kafka集之间的传输。
Kafka架构
接下来我们将从Kafka的架构出发,重点介绍Kafka的主要组件及实现原理。Kafka⽀持消息持久化,消费端是通过主动拉取消息进⾏消息消费的,订阅状态和订阅关系由客户端负责维护,消息消费完后不会⽴刻删除,会保留历史消息,⼀般默认保留7天,因此可以通过在⽀持多订阅者时,消息⽆需复制多分,只需要存储⼀份就可以。下⾯将详细介绍每个组件的实现原理。
1. Producer
Producer是Kafka中的消息⽣产者,主要⽤于⽣产带有特定Topic的消息,⽣产者⽣产的消息通过Topic进⾏归类,保存在Kafka 集的Broker上,具体的是保存在指定的partition 的⽬录下,以Segment的⽅式(.log⽂件和.index⽂件)进⾏存储。
2. Consumer
Consumer是Kafka中的消费者,主要⽤于消费指定Topic的消息,Consumer是通过主动拉取的⽅式从Kafka集中消费消息,消费者⼀定属于某⼀个特定的消费组。
3. Topic
Kafka中的消息是根据Topic进⾏分类的,Topic是⽀持多订阅的,⼀个Topic可以有多个不同的订阅消息的消费者。Kafka集Topic的数量没有限制,同⼀个Topic的数据会被划分在同⼀个⽬录下,⼀个Topic
可以包含1⾄多个分区,所有分区的消息加在⼀起就是⼀个Topic的所有消息。
4. Partition
在Kafka中,为了提升消息的消费速度,可以为每个Topic分配多个Partition,这也是就之前我们说到的,Kafka是⽀持多分区的。默认情况下,⼀个Topic的消息只存放在⼀个分区中。Topic的所有分区的消息合并起来,就是⼀个Topic下的所有消息。每个分区都有⼀个从0开始的编号,每个分区内的数据都是有序的,但是不同分区直接的数据是不能保证有序的,因为不同的分区需要不同的Consumer去消费,每个Partition只能分配⼀个Consumer,但是⼀个Consumer可以同时⼀个Topic的多个Partition。
5. Consumer Group
Kafka中的每⼀个Consumer都归属于⼀个特定的Consumer Group,如果不指定,那么所有的Consumer都属于同⼀个默认的Consumer Group。Consumer Group由⼀个或多个Consumer组成,同⼀个Consumer Group中的Consumer对同⼀条消息只消费⼀次。每个Consumer Group都有⼀个唯⼀的ID,即Group ID,也称之为Group Name。Consumer Group内的所有Consumer协调在⼀起订阅⼀个Topic的所有Partition,且每个Partition只能由⼀个Consuemr Group中的⼀个Consumer进⾏消费,但是可以由不同的Consumer Group中的⼀个Consumer进⾏消费。如下图所⽰:
在层级关系上来说Consumer好⽐是跟Topic对应的,⽽Consumer就对应于Topic下的Partition。Consumer Group中的Consumer数量和Topic下的Partition数量共同决定了消息消费的并发量,且Partition数量决定了最终并发量,因为⼀个Partition只能由⼀个Consumer进⾏消费。当⼀个Consumer Group中Consumer数量超过订阅的Topic下的Partition数量时,Kafka会为每个Partition分配⼀个Consumer,多出来的Consumer会处于空闲状态。当Consumer Group中Consumer数量少于当前定于的Topic中的Partition数量是,单个Consumer将承担多个Partition的消费⼯作。如上图所⽰,Consumer Group B中的每个Consumer需要消费两个Partition中的数据,⽽Consumer Group C中会多出来⼀个空闲的Consumer4。总结下来就是:同⼀个Topic下的Partition数量越多,同⼀时间可以有越多的Consumer进⾏消费,消费的速度就会越快,吞吐量就越⾼。同时,Consumer Group中的Consumer数量需要控制为⼩于等于Partition数量,且最好是整数倍:如1,2,4等。
6. Segment
考虑到消息消费的性能,Kafka中的消息在每个Partition中是以分段的形式进⾏存储的,即每1G消息新建⼀个Segment,每个Segment包含两个⽂件:.log⽂件和.index⽂件。之前我们已经说过,.log⽂件就是Kafka实际存储Producer⽣产的消息,⽽.index⽂件采⽤稀疏索引的⽅式存储.log⽂件中对应消息的逻辑编号和物理偏移地址(offset),以便于加快数据的查询速度。.log⽂件和.index⽂件是⼀⼀对应,成对出现的。下图展⽰了.log⽂件和.index⽂件在Partition中的存在⽅式。
Kafka⾥⾯每⼀条消息都有⾃⼰的逻辑offset(相对偏移量)以及存在物理磁盘上⾯实际的物理地址便宜量Position,也就是说在Kafka中⼀条消息有两个位置:offset(相对偏移量)和position(磁盘物理偏移地址)。在kafka的设计中,将消息的offset作为了Segment⽂件名的⼀部分。Segment⽂件命名规则为:Partition全局的第⼀个Segment从0开始,后续每个segment⽂件名为上⼀个Partition的最⼤offset(Message 的offset,⾮实际物理地偏移地址,实际物理地址需映射到.log中,后⾯会详细介绍在.log⽂件中查询消息的原理)。数值最⼤为64位long⼤⼩,由20位数字表⽰,前置⽤0填充。
上图展⽰了.index⽂件和.log⽂件直接的映射关系,通过上图,我们可以简单介绍⼀下Kafka在Segment中查Message的过程:
  1.根据需要消费的下⼀个消息的offset,这⾥假设是7,使⽤⼆分查在Partition中查到⽂件名⼩于(⼀定要⼩于,因为⽂件名编号等于当前offset的⽂件⾥存的都是⼤于当前offset的消息)当前offset的最
⼤编号的.index⽂件,这⾥⾃然是查到了00000000000000000000.index。
  2.在.index⽂件中,使⽤⼆分查,到offset⼩于或者等于指定offset(这⾥假设是7)的最⼤的offset,这⾥查到的是6,然后获取到index⽂件中offset为6指向的Position(物理偏移地址)为258。
  3.在.log⽂件中,从磁盘位置258开始顺序扫描,直到到offset为7的Message。
⾄此,我们就简单介绍完了Segment的基本组件.index⽂件和.log⽂件的存储和查询原理。但是我们会发现⼀个问题:.index⽂件中的offset 并不是按顺序连续存储的,为什么Kafka要将索引⽂件设计成这种不连续的样⼦?这种不连续的索引设计⽅式称之为稀疏索引,Kafka中采⽤了稀疏索引的⽅式读取索引,kafka每当.log中写⼊了4k⼤⼩的数据,就往.index⾥以追加的写⼊⼀条索引记录。使⽤稀疏索引主要有以下原因:
  (1)索引稀疏存储,可以⼤幅降低.index⽂件占⽤存储空间⼤⼩。
  (2)稀疏索引⽂件较⼩,可以全部读取到内存中,可以避免读取索引的时候进⾏频繁的IO磁盘操作,以便通过索引快速地定位到.log⽂件中的Message。
7. Message
Message是实际发送和订阅的信息是实际载体,Producer发送到Kafka集中的每条消息,都被Kafka包装成了⼀个Message对象,之后再存储在磁盘中,⽽不是直接存储的。Message在磁盘中的物理结构如下所⽰。
On-disk format of a message
offset        : 8 bytes
message length : 4 bytes (value: 4 + 1 + 1 + 8(if magic value > 0) + 4 + K + 4 + V)
crc            : 4 bytes
magic value    : 1 byte
attributes    : 1 byte
timestamp      : 8 bytes (Only exists when magic value is greater than zero)
key length    : 4 bytes
key            : K bytes
value length  : 4 bytes
value          : V bytes
其中key和value存储的是实际的Message内容,长度不固定,⽽其他都是对Message内容的统计和描述,长度固定。因此在查实际Message过程中,磁盘指针会根据Message的offset和message length计算移动位数,以加速Message的查过程。之所以可以这样加速,因为Kafka的.log⽂件都是顺序写的,往磁盘上写数据时,就是追加数据,没有随机写的操作。
8.Partition Replicas
最后我们简单聊⼀下Kafka中的Partition Replicas(分区副本)机制,0.8版本以前的Kafka是没有副本机制的。创建Topic时,可以为Topic 指定分区,也可以指定副本个数。kafka 中的分区副本如下图所⽰:
Kafka通过副本因⼦(replication-factor)控制消息副本保存在⼏个Broker(服务器)上,⼀般情况下副本数等于Broker的个数,且同⼀个副本因⼦不能放在同⼀个Broker中。副本因⼦是以分区为单位且区分⾓⾊;主副本称之为Leader(任何时刻只有⼀个),从副本称之为Follower(可以有多个),处于同步状态的副本叫做in-sync-replicas(ISR)。Leader负责读写数据,Follower不负责对外提供数据读写,只从Leader同步数据,消费者和⽣产者都是从leader读写数据,不与follower交互,因此Kafka并不是读写分
离的。同时使⽤Leader进⾏读写的好处是,降低了数据同步带来的数据读取延迟,因为Follower只能从Leader同步完数据之后才能对外提供读取服务。
如果⼀个分区有三个副本因⼦,就算其中⼀个挂掉,那么只会剩下的两个中,选择⼀个leader,如下图所⽰。但不会在其他的broker中,另启动⼀个副本(因为在另⼀台启动的话,必然存在数据拷贝和传输,会长时间占⽤⽹络IO,Kafka是⼀个⾼吞吐量的消息系统,这个情况不允许发⽣)。如果指定分区的所有副本都挂了,Consumer如果发送数据到指定分区的话,将写⼊不成功。Consumer发送到指定Partition的消息,会⾸先写⼊到Leader Partition中,写完后还需要把消息写⼊到ISR列表⾥⾯的其它分区副本中,写完之后这个消息才能提交offset。
到这⾥,差不多把Kafka的架构和基本原理简单介绍完了。Kafka为了实现⾼吞吐量和容错,还引⼊了很多优秀的设计思路,如零拷贝,⾼并发⽹络设计,顺序存储,以后有时间再说。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。