五千字让你了解的Kafka重点原理
Posted oahaijgnahz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五千字让你了解的Kafka重点原理相关的知识,希望对你有一定的参考价值。
Kafka原理解析
文章目录
一、什么是消息队列?什么是Kafka?
1.1 消息队列
消息队列就是用于数据生产方和消费方解耦合的中间件。顾名思义,主体就是一个队列的形式收集消息,数据在消费端按照FIFO的原则被消费。
1.2 Kafka概念与基础架构
Kafka是一个基于发布订阅模式的分布式消息队列
Kafka基础架构如下图所示:
- 为了方便扩展和提高吞吐量,每个Topic分为多个Partition。
- 配合Topic分区的设计,提出消费者组的概念,每个组内的消费者并行消费一个Topic中的不同Partition中的数据。(但是整体上还是一个Topic为一个队列,并且消费者数与分区数可以不同)
- 为了高可用性,为每个Partition在不同broker节点存放了副本。(仅仅是Follower不接受请求)
二、Kafka架构深入!!
2.1 Kafka存储模型
Kafka中消息是以Topic进行分类的,生产者生产消息,消费者消费消息,都是面向Topic的。而Topic在物理上的存储是分区存储的,即按Partition分布式存储。每个Partition中的数据又是顺序写入log文件1中进行存储。
但这样还是会出现分区log文件过大,导致的读取性能下降的问题。所以Kafka将log文件切分成了segment,每个segment由 .log数据存储文件 和 .index索引文件 和 .timeindex文件组成。详细的结构如下图所示:
而每个log文件和index文件的命名就是 文件中起始数据的偏移量,一个segment中由index定位到对应log文件中执行数据的原理如下图:
index文件中根据需要查找的offset根据保存起始偏移量(文件名)的相对偏移量,定位到log中数据真实的位置。
- 1注解1:类似HBase中的顺序写入HFile,磁头寻址次数少,顺序读/写性能好。
2.2 Kafka Producer
2.2.1 数据分区
如前面所提到的,Kafka是分布式的消息队列,分区的目的是:
充分利用分布式的优势
- 分区方便后期拓展
- 分区能够增大读写的吞吐量。
分区原则:指定了Partition的直接写入对应Partition、否则根据key进行hash后对分区数取余、没有key的通过random-robin算法得到分区(第一个得到一个随机数,后续的在此基础上自增)
2.2.2 数据可靠性保证
为保证Producer发送的数据,能可靠的发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送ack响应,如果Producer收到ack,就会进行下一轮的发送(不是同步的,而是异步的,分批次检查前面发送的消息ack是否收到),否则重新发送数据。
- ack应答机制:Kafka设定了三种类型的应答机制
acks = 0
:Producer不用等待broker的ack,但是broker未将数据写入前宕机会产生数据丢失。
acks = 1
:Producer等待broker的ack,Partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会数据丢失。
acks = -1
(常用):Producer等待broker的ack,Partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复。
- ISR:ISR是用于解决Partition的leader等待所有follower同步,然而某些follower由于某些原因迟迟不能完成同步的问题的follower动态集合。那些无法完成同步的follower会被踢出集合;在其恢复后,同步了follower数据后再重新加入集合。
- 故障恢复:
-
follower故障后会被踢出ISR,在其恢复后获取集合的HW(high watermark,ISR中所有副本中LEO的最小值,LEO则是每个副本待写入的偏移量),重新向leader同步HW前的数据。完成后重新加入ISR。
-
leader故障后,ZK在ISR中选出新的leader,为保证副本数据一致性,follower会将自身HW后的数据截断,重新向新的leader同步。
*注意:上述只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
2.2.3 Exactly-Once语义
在保证数据不丢失的场景下,一般将Kafka的ack应答设置为-1,那么也就存在数据重复的可能性(At-Least-Once)。而要实现数据的Exactly-Once(每条数据在Kafka中有且只有一条不会重复也不会丢失),需要额外实现幂等性,也即:
At-Least-Once + 幂等性 = Exactly-Once
为了实现幂等性,Producer端会生成一个PID,Producer发往同一个Partition的消息会附带Sequence Number(offset);而Broker端会对<PID,Partition,SeqNum>
进行缓存,当收到相同主键的消息并且Sequence Number值比缓存值小则不再重复存储。
幂等性在跨分区跨会话时会失效(当Producer挂了重启后,主键PID会发生变化) ——需要通过事务解决
2.2.4 Producer数据提交流程
Kafka的Producer发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程——main线程和Sender线程,以及一个线程共享变量——RecordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。
注意:
1.消息在Producer端就已经分区了。
2.消息是按照batch size发送的,但当数据未达到batch size超时了也会被强制发送。
2.3 Kafka Consumer
2.3.1 消费模式
Kafka的消费模式是poll模式,就是每个消费者按照自己的消费能力在Broker中读取数据。但有一个问题就是,如果没有新的数据,那么消费者就在循环中空转,这个问题Kafka设置了一个短暂的timeout来让消费者在没有数据可以消费的时候等待一小会。
2.3.2 分区分配策略与重平衡(rebalance)
如果Consumer组中Consumer数量 < Topic Partition数量
根据Random-Robin策略(实际是个轮询,对多个topic的分配较为均衡)或者Range(划分范围,一个范围内的给到一个消费者,只保证每个topic分配的相对均衡)对Partition进行分配。此后这个消费者组中的消费者消费的Partition就被定下来了(若是Consumer数量 > Topic Partition数量
,则多出来的消费者会被闲置)。
重平衡问题:重平衡需要借助 Kafka Broker 端的 Coordinator 组件(每个Broker都有,分配消费Topic对应分区的时候找到分区Leader所在的Broker的Coordinator)。
-
有三个条件会触发重平衡:
- 消费者组内成员发生变更,这个变更包括了增加和减少消费者。注意这里的减少有很大的可能是被动的,就是某个消费者崩溃退出了(或者心跳消息由于网络原因延时过大)
- 主题的分区数发生变更,Kafka目前只支持增加分区,当增加的时候就会触发重平衡
- 订阅的主题发生变化(新增主题,比如消费者组使用正则表达式订阅主题),而恰好又新建了对应的主题,就会触发重平衡
-
重平衡的弊端:因为重平衡过程中,消费者无法从Kafka消费消息,这对Kafka的TPS影响极大,而如果Kafka集内节点较多,比如数百个,那重平衡可能会耗时极多。
-
Rebalance 过程分为两步:Join 和 Sync。
-
Join,顾名思义就是加入组。这一步中,所有成员都向Coordinator发送JoinGroup请求,请求加入消费组。一旦所有成员都发送了JoinGroup请求,Coordinator会从中选择一个Consumer担任Leader的角色,并把组成员信息以及订阅信息发给Leader。Leader负责消费分配方案的制定。
-
Sync,这一步Leader开始分配消费方案,即哪个Consumer负责消费哪些Topic的哪些Partition。一旦完成分配,Leader会将这个方案封装进SyncGroup请求中发给Coordinator,非Leader也会发SyncGroup请求,只是内容为空。Coordinator接收到分配方案之后会把方案塞进SyncGroup的response中发给各个Consumer。这样组内的所有成员就都知道自己应该消费哪些分区了。
如果是Topic增加或者Partion增加,则由Leader consumer通知Coordinator进行rebalance
2.3.3 offset维护
comsumer需要记录已经消费到的偏移量以便故障或者后续继续消费。在0.9版本后Kafka将这些信息都保存在一个内置的Topic中(__comsumer_offset
),而此前的版本是保存在ZK中的(1.优化效率,减轻ZK压力 2.可以自己实现偏移量维护)
2.4 Kafka高效读写的保证~
- 如同HBase顺序写HFile文件一样,Kafka顺序写log文件写入磁盘效率极高,并且分段存储并使用索引文件加快查找(据Kafka官网文档说比随机写快6000倍)。
- 使用操作系统的Page Cache来缓存要写入的数据,好处在于:
(1)写入前可以做一些优化,提高磁盘写入性能
(2)缓存也可以用于数据被读取,当数据写入与读取速率相近的情况下,可以直接内存读取。
(3)Page Cache非JVM内存,不会影响JVM,导致GC的增加。同时,Kafka节点宕机,数据还在此机器缓存。 - 零拷贝机制:
很多应用其实在文件从磁盘拷贝到磁盘、从磁盘拷贝到Socket缓存,这些应用不需要接手这些数据。而一般的拷贝机制要经历从例如磁盘 -> 内核page cache -> 应用缓存 -> 内核page cache -> 磁盘的过程,如果数据只是单纯的拷贝而不需要修改,那么拷贝到应用缓存的步骤完全是多余的。所以Kafka利用了操作系统提供的零拷贝机制,来减少不需要的系统调用和数据拷贝次数。
2.5 Kafka如何通过ZK来进行选举和状态更新?
首先,Kafka集群启动时,会从Broker中选举一个Controller(分布式锁实现抢先创建临时节点的broker当选),负责管理集群Broker的上下线(监听zk的/brokers/ids/节点)、所有Topic分区副本分配、leader选举等工作。
当某个Broker挂了以后,Controller监听到临时节点/brokers/ids/中的变化,从ZK各个分区状态信息中获取ISR(此时去除了挂掉节点所有的Partition,失去leader的Partition重新选举leader),并完成ZK各个分区状态更新
2.6 Kafka事务!
Kafka事务在0.11版本后引入,主要解决的是 Producer在Exactly Once语义上跨分区跨会话的精准一次写入,要么成功要么失败。
- Producer事务(断点续传)
为了实现跨分区跨会话的事务,每个Producer需要自己提供一个全局唯一的Transaction ID(对相同的Transaction ID,通过维护一个递增producer epoch来使得相同Producer只有最新事务有效),并将Producer获得的PID和Transaction ID绑定。这样当Producer重启后就可以通过正在进行的Transaction ID获得原来的PID。
为了管理Transaction,Kafka引入了一个新的组件Transaction Coordinator(事务调度器)。Producer就是通过和Transaction Coordinator交互获得绑定的PID和对应的任务状态。Transaction Coordinator还负责将事务所有写入Kafka的一个内部Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。
*注意:Kakfa事务回滚不会直接去删除消息,而是将消息对Consumer不可见。
- Consumer事务
Kafka对Consumer的事务较弱,一般是通过Consumer端自己实现精确一次性消费(将消费过程和提交offset作为一个原子操作实现)。
2.7 手动维护offset
实现精准一次性消费,手动维护offset是基础,但也无法避免重复消费
Kafka消费偏移量的维护默认由Kafka通过维护Topic(__comsumer_offset
)来实现,默认5s自动提交一次。但这样做Kafka在记录偏移量前宕机,则消费者会重复消费。
单纯的手动提交偏移量如果消费行为和手动提交行为不是一个原子行为,那么消费者在消费完未提交偏移量期间宕机,数据会发生重复消费的现象。解决方法有两种:
- 在消费者端去重,在每次宕机重启后对新消费数据去重。
- 将消费和偏移量提交绑定为原子化操作(事务),消费端将offset维护到外部介质中。
以上是关于五千字让你了解的Kafka重点原理的主要内容,如果未能解决你的问题,请参考以下文章
三万五千字长文!让你懂透编译原理——第六章 属性文法和语法制导翻译