kafka架构和原理分析

5 篇文章 0 订阅
1 篇文章 0 订阅

目录

kafka消费模式

kafka架构

kafka生产者消息发送流程

文件存储机制

kafka生产者分区策略

kafka数据可靠性与一致性

Exactly Once

kafka消费者分区策略

consumer offset的维护

kafka读写效率为什么这么高

zookeeper在kafka中的作用

kafka事务

producer事务

consumer事务


kafka消费模式

kafka是一个分布式的、基于发布/订阅模式的消息队列。

基于发布、订阅模式的消息队列有两种消费模式:

一种是pruducer发送数据后,由消息队列push数据给到consumer,由于消息队列控制着数据传输的速率,而不同consumer消费速率不一致,当消费速率低于生产速率时,consumer就忙不过来了。

kafka采用的另一种消费模式,pruducer把数据push到消息队列,然后consumer从消息队列中pull数据。由consumer自己来决定消费速率。当 consumer 速率落后于 producer 时,可以在适当的时间赶上来。这种模式也有其不足之处,在没有消息时,consumer一直在空轮询,等待数据的到来。

kafka架构

总体架构图:

broker:一个kafka服务器就是一个broker,kafka集群由多个broker组成。一个broker下可以有多个topic

producer:消息生产者,向kafka broker发消息的客户端,直接发送数据到主分区的broker上,不需要经过任何中间路由

consumer:消息消费者,从kafka broker 中pull消息的客户端

topic:消息的分类,生产者 和消费者都是面向topic

partition:分区,一个topic可以分为多个partition,每个partion是一个有序的队列

replica:partition的副本,kafka为了防止数据丢失,每个partion会有多个副本,一个Leader和多个Follower,分布到不同的broker中副本的数量不能超过broker的数量。

leader:partition主分区,producer发送消息和consumer消费消息的对象

follower:从分区,从leader中同步数据,当leader出现故障时,follower会成为新的leader

consumer group:消费者组,由多个consumer组成。同一消费组内,每个分区只能由某一个消费者消费;多个消费者组可以同时消费。

kafka生产者消息发送流程

生产者消息写入流程:

  • 1.producer 发送消息给到partition leader
  • 2.leader将数据写入log
  • 3.follower向leader同步消息,写入log后,向leader发送ack
  • 4.leader收到follower的ack,向producer发送ack

文件存储机制

kafka以topic对消息进行分类,一个topic可以有多个partition,每个partition物理上对应一个文件夹,其文件夹的命名
规则为:topic 名称+分区序号,例如topicA有3个分区,对应文件夹名称为:topicA-0,topicA-1,topicA-2。文件夹中存储了该partition的所有消息数据和索引文件,producer发送的消息数据存储在文件夹下的log文件中,producer发送的数据会不断向log文件追加。

为了防止log文件过大导致数据访问效率低下,kafka采取了分片和索引的机制,每个partition分成多个segment,每个segment对应有2个文件:.index 和 .log文件

00000000000000000000.index
00000000000000000000.log
00000000000000184635.index
00000000000000184635.log
00000000000000324567.index
00000000000000324567.log

log数据文件和index索引文件的命名规则:上一个 segment文件最后一条消息的 offset 值进行递增。
log文件存储的是massage数据,index文件存储的是相对offset和position物理偏移量。

  • 相对offset:partition分段之后,每个数据文件的起始offset不为0,相对offset表示一条message相对于其所属index文件中起始offset的大小。例如,一个index文件的offset是从10000开始,offset为10015的message在index文件中的相对offset就是10015-10000 = 15。存储相对offset可以减小索引文件占用的空间。
  • position物理偏移量:message在log数据文件中的绝对物理位置,可以理解为message在数据文件中的指针。

segment index file采取稀疏索引存储方式,并没有为数据文件中的每条message建立索引,每隔一定字节的数据建立一条索引。减少了索引文件占用空间,可以通过mmap直接在内存当中操作索引,减少系统调用的次数,提高查询效率。稀疏索引为数据文件的message设置了一个元数据指针(position),它比稠密索引节省了更多的存储空间,但查找起来需要消耗更多的时间。

message物理结构:

参数如下:

关键字解释说明
8 byte offset在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message
4 byte message sizemessage大小
4 byte CRC32用crc32校验message
1 byte “magic"表示本次发布Kafka服务程序协议版本号
1 byte “attributes"表示为独立版本、或标识压缩类型、或编码类型。
4 byte key length表示key的长度,当key为-1时,K byte key字段不填
K byte key可选
value bytes payload表示实际消息数据。

segment下,index索引和log数据文件对应关系如下:

在partition中通过offset查找message的步骤:

  • 1.跟据offset值,通过二分查找法,快速定位到segment对应的index和log文件。
  • 2.根据offset值,在index文件中找出匹配范围内的偏移量position。
  • 3.得到position,到log文件中,从position处开始顺序查找,将每条message的 offset 与目标 offset 进行比较,直到找到为止

例如,查找offset为368773的message:

  • 通过二分法找到文件名<=368773的最大segment,如找到了名为368769的文件
  • 368773-368769=4。在对应的index文件中寻找<=4的最大相对offset对应的position,即 [3,497] 这条记录
  • 再到log文件中,找到position为497的message, 按照顺序查找,比较每条消息的 offset 是否为368773,找到后返回

kafka生产者分区策略

1.为什么要分区?

  • 更方便的支持集群横向扩展,提高并发和负载能力。

2.分区的原则

kafka 是根据message key 来决定message落到哪个分区上的

  • (1)可以手工指定partition,指明partition的值即可
  • (2)没有指定partition,但是有指定message key的情况下,使用key的hash值与对应topic下的partition数量进行取模运算得到partion的值
  • (3)没有指定partition,也没有指定key的情况下,首次调用时随机生成一个整数(后面持续递增),用这个整数值和partition的数量取模得到partition的值。kafka默认分区策略就是这种,也叫做round-ribon(轮询策略)。

kafka数据可靠性与一致性

kafka多分区副本架构是可靠性保证的核心,每个partion多份数据备份存储在不同机器上,保证消息的持久化。

1.多副本数据同步策略

为保障producer发送的消息能可靠的发送到指定的topic,topic下的每个partition接收到消息后,都要向producer发送ack,确认收到消息。如果producer收到ack,就会进行下一轮消息发送,否则,重新发送消息。

  • 什么时候发送ack?
    确保有follower与leader同步成功,leader再发送ack,这样才能保证leader挂了之后,follower数据的完整性
  • 那么多少个follower同步成功后发送ack?
    方案一:半数以上的follower同步成功,就发送ack
    方案二:所有follower同步成功后,再发送ack
方案优点不足
半数以上完成同步,就发
送ack
延迟低选举新的leader 时,容忍n 台节点的故障,需要2n+1 个副
全部完成同步,才发送
ack
选举新的leader 时,容忍n 台节点的故障,需要n+1 个副
延迟高

kafka选用的是第二种方案,对于kafka而言,每个partition都有大量数据,使用方案一,会造成大量数据冗余,占用资源成本高

2.同步副本ISR

kafka要确保所有follower完成同步后,leader才会发送ack,设想:有一个follower,发生故障,迟迟不能与leader 进行同步,那leader 就要一直等下去,这个问题怎么解决?

这就引出了ISR(in-sync replica set),也叫同步副本,每个分区的leader维护了一个动态的ISR,意为和leader 保持同步的follower 集
合。只有跟的上leader的follower才能加入ISR,如果follower长时间未向leader 同步数据, 则该follower 将被踢出ISR,该时间阈值由replica.lag.time.max.ms 参数设定。Leader 发生故障之后,就会从ISR 中选举新的leader。

3.ack 应答机制

有些场景下,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR 中的follower 全部接收成功。
所以Kafka 为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,自行选择。

  • acks参数配置:
    0:producer 不等待broker 的ack,这一操作提供了一个最低的延迟,broker 一接收到还
    没有写入磁盘就已经返回,当broker 故障时有可能丢失数据。
    1:produer等待broker的ack,在partition的leader数据持久化到磁盘后返回ack,如果在follower
    同步成功之前leader 故障,那么将会丢失数据
    -1(all):produer等待broker的ack,在partition的leader和follower都持久化成功后返回ack,
    但是如果在follower 同步完成后,broker 发送ack 之前,leader 发生故障,那么会
    造成数据重复

4.故障处理

kafka的每个分区副本都有两个重要的属性:LEO和HW。注意是所有的副本,不只是leader副本。

  • LEO(log end offset):即日志末端位移,记录了该副本log文件中下一条消息的offset
  • HW:高水位,对于同一个副本而已,其HW值都不会大于LEO,表示“已备份状态”的消息。
    leader副本的HW值也就是分区HW值,代表实际已提交消息的范围。

kafka有两套follower副本LEO:

  • 第一套LEO保存在follower副本所在broker中
  • 第二套LEO保存在leader副本所在的brok中,也就是说leader副本机器上保存了所有follower副本的LEO。

那么为什么要保存两套呢?

因为kafka要使用第一套LEO帮助follower更新其HW值,使用第二套LEO帮助leader更新HW。

producer 发送消息到partition leader,leader收到消息并成功写入log后,leader LEO就+1

follower发送fetch请求并得到leader的数据响应,成功写入log后,follower LEO+1
再比较本地LEO值和leader中HW值,选则小的作为follower中的HW值

leader收到follower的fetch请求后,拉取对应消息的log,在响应follower前,remote LEO+1(leader上保存的follower LEO)。
leader会筛选出ISR中所有符合条件的partition'副本,比较所有的LEO,选择最小的LEO值作为HW。

(1)follower故障

follower发生故障后会被踢出ISR,等到该follower恢复后,会读取本地磁盘上记录的HW,将log文件中高于HW的部分截取掉,从HW开始向leader同步,等该follower的LEO大于等于partition的HW,HW,即follower 追上leader 之后,就可以重新加入ISR

(2)leader故障

leader发生故障之后,会从ISR中选出一个新的leader,新的leader会尝试去更新分区HW,再通知其它的follower把各自的log文件中高于HW部分截取掉,之后从新的leader同步数据

Exactly Once

将kafka的ack级别设置为-1,可以保证producer到broker之间不会丢失睡觉,即At Least Once。
将kafka的ack级别设置为0,可以保证生产者每条消息至多发送一次,即At Most Once。

At Least Once可以保证数据不丢失,但不能保证数据不重复。At Most Once 可以保证数据不重复,不能保证数据不丢失。但最好的效果当然是数据不重复也不丢失,即Exactly Once语义。
0.11版本后,kafka推出一重大新特性:幂等性。即同一条消息,不论producer向broker发送多少次数据,broker都只会持久化一条。在ack设置为-1的前提下,幂等性结合At Least Once 就构成了Exactly Once语义:At Least Once + 幂等性 = Exactly Once

要启用幂等机制,需要将producer的参数enable.idompotence设置为true,开启幂等性后,producer在初始化会分配一个PID,发往同一partition的消息会携带Sequence Number,broker对<PID, Partition, SeqNumber>做缓存, 当具有相同主键的消息提交时,broker只会持久化一条。

注意:0.11版本之前,在producer客户端重启后broker会分配新的PID。

kafka消费者分区策略

kafka的consumer采用pull的方式从broker中拉取消息,如果kafka 没有数据,消费者可能会陷入循环中,一直返回空数
据。针对这种场景,kafka在消费消息时会传入一个timeout参数,如果当前没有可供消费,那么consumer会等待timeout时长之后再返回。

在kafka中,一个consumer group由一到多个consumer组成。而producer发送给topic的数据是落到多个partition上的,一个partition只能被同一consumer group中的某一个consumer消费。consumer要消费消息,必然面临partition分配问题。

kafka消费者分区策略有三种:RoundRobin、Range、Sticky。当consumer的数量发生变动的时候,会触发消费者partition重新分配

  • RoundRobin

RoundRobin就是轮询的意思,这种策略以consumer group为整体,拿到consumer group中所有Consumer 订阅的 TopicPartition后,根据TopicAndPartition的hash值对partition进行排序,按照顺序分发给consumer,如果组内每个消费者的topic是同样的,那么partition的分配是均匀的。如果组内每个consumer订阅的topic不相同,可能会造成消费数据混乱(没有订阅该topic的consumer却有可能消费到该topic的消息)

  • Range

Range是以每个topic作为一个单独的整体,只有订阅了该topic的consumer才能消费到该topic的消息。用partition的数量除以consumer的数量,按照平均范围分配给Consumer,因为分区数可能无法被消费者数量整除,多出的parition追加到 前面的consumer,可能造成分区分配不均匀。Kafka默认采用RangeAssignor的分配算法

  • Sticky

Kafka从0.11 版本开始引入Sticky,主要有两大特性:
1.主题分区的分配要尽可能的均匀;
2.当Rebalance 发生时,尽可能保持上一次的分配方案。
这样一种实现可以使消息分配更加均匀,减少了不必要的操作节约系统资源。

consumer offset的维护

由于consumer在消费过程可能会出现宕机等故障,为了保证consumer在恢复后,能从上一次消费的位置继续消费,kafka需要把消费的offset存储起来。offset是以group+topic+partition存储的,在0.9版本以前,offset是存储在zookeeper中的,0.9版本之后,offset是存储在内部一个topic中的,该topic 为__consumer_offsets

要消费kafka内置topic需要修改配置文件consumer.properties:exclude.internal.topics=false

kafka读写效率为什么这么高

kafka数据是以文件的形式存储在磁盘中的,是如何做到如此高效的呢?
Kafka读写效率高的秘诀在于,它把所有的消息都存储在文件中。通过mmap提高I/O写入速度,写入数据的时候它是末尾添加所以速度很快;同时为了减少磁盘的写入的次数,broker会将消息暂存到buffer中,当消息数量到达一定阈值的时候,再flush到磁盘,这样更减少了磁盘IO的调用次数。而consumer端也是批量fetch多条消息,读取数据的时候配合sendfile直接输出

  • 顺序写

kafka生产消息使用的是顺序写,数据进入到分区的消息队列尾部,这样的磁盘顺序写比传统的BTREE随机写性能高了很多。磁盘顺序写的速度甚至比内存随机写都快。
消费者与生产者互相不干扰,消费者读取消息队列的头部,生产者读取消息队列的尾部。 这样的方式无写锁,读锁。性能非常高。

  • MMAP

mmap:内存映射文件
写入数据:应用程序调用了mmap()之后,会打通用户空间和内核空间,用户空间和内核空间共享一块缓冲区,并且MMAP直接映射到磁盘上的某个文件,完成映射之后对物理内存的操作会被同步到硬盘上
客户端发送数据到达系统内核缓冲区,数据从内核拷贝到用户空间程序,程序处理完之后,直接将数据放入共享缓冲区,写入到磁盘

如果没有MMAP,要写入数据,客户端先发送数据到达系统内核,数据从内核拷贝到用户空间程序,程序处理完之后,会调用系统内核的write写,数据先写入到内存,再落到磁盘上

  • 零拷贝

读取数据:用户空间调用系统内核sendfile指令,内核读取到数据后直接通过网卡发送给客户端。

如果不使用零拷贝技术,要读取数据,用户空间程序先调用系统内核的read指令,数据从磁盘上拷贝到内存,内核读取数据,拷贝到用户空间程序,程序调用内核write写将数据通过网卡发送给客户端

zookeeper在kafka中的作用

kafka集群是分布式部署,broker之间相互独立,这个时候就需要有一个系统来管理broker节点,这个时候就用到了zookeeper。所有的kafka broker节点一起去zookeeper上注册一个临时节点,只有一个broker能够注册成功,这个broker会成功kafka集群的Controler,负责管理集群broker 的上下线。负责topic分区副本分配,leader选举等工作。

kafka事务

Kafka 从0.11 版本开始引入了事务支持。事务可以保证Kafka 在Exactly Once 语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。

producer事务

为了实现跨分区会话的事务,需要引入一个全局唯一的Transaction ID,这个Transaction ID是由客户端定义并传入的,再将producer的PID和Transaction ID绑定,这样一来,producer在重启后,就能通过正在进行的Transaction ID获得原来的PID,而不用重新向broker申请一个PID,通过这种机制,kafka就能保证跨分区跨会话的Exactly Once

为了管理Transaction ,kafka引入了新组件Transaction Coordinator,Producer和Transaction Coordinator交互,获得Transaction ID对应的任务状态,并且Transaction Coordinator还负责将事务写入kafka内部的一个topic,即使整个服务重启,由于事务状态保存起来了,进行中的事务状态还是可以得到恢复,继续进行下去

consumer事务

对于consumer而言,事务的保证相对弱些,尤其是无法保证commit的消息被精确消费,这是由于consumer可以通过offset访问任意消息,而不同segment file的生命周期不一样,同一事务的消息可能会出现重启后被删除的情况。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值