目录
一、Kafka数据存储方式
名词解释
分区分步示意图
Kafka数据存放
Kafka如何通过offset查找到Message
二、Kafka如何确保数据不丢失
三、Kafka可以支持高吞吐量的原因
四、Kafka选举策略
什么是ISR
什么是LEO、LSO、HW、LW
数据更新过程
Follwer同步数据
分区 Leader故障转移&选举策略
Broker:Kafka节点,一个Kafka节点就是一个broker,多个broker可以组成一个Kafka集群
Topic:一类消息,消息存放的目录即主题,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能够同时负责多个topic的分发
message: Kafka中最基本的传递对象。
Partition:topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列
每个分区都有一台 server 作为 “leader”,零台或者多台server作为 follwers 。leader server 处理一切对 partition (分区)的读写请求,而follwers只需被动的同步leader上的数据。当leader宕机了,followers 中的一台服务器会自动成为新的 leader。
Replica:副本,为实现备份的功能,保证集群中的某个节点发生故障时,该节点上的 Partition 数据不丢失,且 Kafka 仍然能够继续工作,Kafka 提供了副本机制,一个 Topic 的每个分区都有若干个副本,一个 Leader 和若干个 Follower。
Segment:partition物理上由多个segment组成,每个Segment存着message信息
Producer : 生产者,生产message发送到topic
Consumer : 消费者,订阅topic并消费message, consumer作为一个线程来消费
Consumer Group:消费者组,一个Consumer Group包含多个consumer
Offset:偏移量,理解为消息partition中的索引即可
创建一个6个分区,3个副本的topic
@Bean
public NewTopic myTopic() {
return new NewTopic("my-topic-partition", 6, (short) 3);
}
通过ZKtools可知几个分区分步如下。 工具下载地址,免费下载哦 zktools.rar-桌面系统文档类资源-CSDN下载
partition1:{"controller_epoch":4,"leader":1,"version":1,"leader_epoch":0,"isr":[1,3,2]}
partition2:{"controller_epoch":4,"leader":2,"version":1,"leader_epoch":0,"isr":[2,1,3]}
partition3:{"controller_epoch":4,"leader":3,"version":1,"leader_epoch":0,"isr":[3,2,1]}
partition4:{"controller_epoch":4,"leader":1,"version":1,"leader_epoch":0,"isr":[1,2,3]}
partition5:{"controller_epoch":4,"leader":2,"version":1,"leader_epoch":0,"isr":[2,3,1]}
partition6:{"controller_epoch":4,"leader":3,"version":1,"leader_epoch":0,"isr":[3,1,2]}
其中controller_epoch表示的是当前的kafka控制器,leader表示当前分区的leader副本所在的broker的id编号,version表示版本号(当前半本固定位1),leader_epoch表示当前分区的leader纪元,isr表示变更后的isr列表(后面解释什么ISR)。
由图可见,每一个Broker都冗余了每个分区的数据。我们称为副本机制。这样有以下优点
在一个分区中,会将一个大的分区拆分n多个不同小segment文件 ,每个segment文件 存放我们该分区日志消息。在每个segment中会有.index、.log。在默认的情况下,每个segment文件容量最大是为1073741824KB(1024MB),如果超过的情况下依次内推,产生一个新的segment文件,可以通过修改配置文件log.segment.bytes=1073741824修改。
00000000000000000000.index-----消息偏移量索引文件
00000000000000000000.log-----消息持久化内容
如上图假如第一个分区存放的offset到1000,那么下一个文件的命名从上一个offset位置结束的位置开始。
如下图是文件存储的样子
总结: 每个分区是由多个segment组成,每个segment由多个index和多个log文件组成,并且是按照一定的顺序存放数据的。
命名规则
每个segment file也有自己的命名规则,每个名字有20个字符,不够用0填充,每个名字从0开始命名,下一个segment file文件的名字就是,上一个segment file中最后一条消息的索引值。在.index文件中,存储的是key-value格式的,key代表在.log中按顺序开始顺序消费的offset值,value代表该消息的物理消息存放位置。但是在.index中不是对每条消息都做记录,它是每隔一些消息记录一次(稀疏索引),避免占用太多内存。即使消息不在index记录中,在已有的记录中查找,范围也大大缩小了。
如何查看Kafka日志和index文件
#index
./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /kafka/kafka-logs-689fb31d544a/my-topic-partition-1/00000000000000000000.index
#log
./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /kafka/kafka-logs-689fb31d544a/my-topic-partition-1/00000000000000000000.log
Index文件
Log文件
小结:
1、topic是逻辑概念,partition是物理概念。
2、.log文件存放实际数据,生产者的数据都会追加到.log文件中。
3、为防止.log文件过大导致数据定位效率低下,kafka采取了分片(segment)和稀疏索引机制,将partition分为多个segment,分别进行索引。
4、.index文件存储大量的索引信息,.log文件存储大量的数据,索引文件中的元数据指向对应数据文件中Message的物理偏移地址。
生产者的ack机制
向 Kafka 写数据时,producers 设置 ack 是否提交完成。
注意:默认情况下,当 acks = all 时,只要 ISR 副本同步完成,就会返回消息已经写入。例如,一个 topic 仅仅设置了两个副本,那么只有一个 ISR 副本,那么当设置acks = all时返回写入成功时,剩下了的那个副本数据也可能数据没有写入。
消费者的offset commit
消费者通过offset commit 来保证数据的不丢失,kafka自己记录了每次消费的offset数值,下次继续消费的时候,会接着上次的offset进行消费。kafka并不像其他消息队列,消费完消息之后,会将数据从队列中删除,而是维护了一个日志文件,通过时间和储存大小进行日志删除策略。默认情况下每隔 5分钟(log.retention.check.interval.ms=300000)会检测一次是否有日志文件需要删除。日志文件会保留log.retention.hours=168小时(7天),当日志文件超过(log.retention.bytes=1073741824)1024MB(与时间保留策略独立)都会进行删除。如果offset没有提交,程序提交之后,会从上次消费的位置继续消费,有可能存在重复消费的情况。
Offset Reset 三种模式
broker的副本 机制
每个broker中的partition我们一般都会设置有replication(副本)的个数,生产者写入的时候首先根据分发策略(有partition按partition,有key按key,都没有轮询)写入到leader中,follower(副本)再跟leader同步数据,这样有了备份,也可以保证消息数据的不丢失。
1、顺序读写:基于磁盘的随机读写确实很慢,但磁盘的顺序读写性能却很高,一些情况下磁盘顺序读写性能甚至要高于内存随机读写。(Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升 。)
2、Page Cache:为了优化读写性能,kafka利用了操作系统本身的page cache,就是利用操作系统自身的内存而不是JVM空间内存,这样做的好处是:
a:避免Object消耗:如果是使用java堆,java对象的内存消耗比较大,通常是所存储数据的两倍甚至更多。
b:避免GC问题:随着JVM中数据不断增多,垃圾回收将会变得复杂与缓慢,使用系统缓存就不会存在GC问题。
通过操作系统的page cache,kafka的读写操作基本上是基于内存的,读写速度得到了极大的提升。
3、零拷贝:(不使用的时候,数据在内核空间和用户空间之间穿梭了两次),使用零拷贝技术后避免了这种拷贝。通过这种 “零拷贝” 的机制,Page Cache 结合 sendfile 方法,Kafka消费端的性能也大幅提升。这也是为什么有时候消费端在不断消费数据时,我们并没有看到磁盘io比较高,此刻正是操作系统缓存在提供数据。
4、分区分段+索引:topic 中的数据是按照一个一个的partition即分区存储到不同broker节点的,每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的,这也非常符合分布式系统分区分桶的设计思想。kafka的message消息实际上是分布式存储在一个一个segment中的,每次文件操作也是直接操作的segment。为了进一步的查询优化,kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件.这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据处理的并行度。
5、批量读写:Kafka数据读写也是批量的而不是单条的。在向Kafka写入数据时,可以启用批次写入,这样可以避免在网络上频繁传输单个消息带来的延迟和带宽开销。假设网络带宽为10MB/S,一次性传输10MB的消息比传输1KB的消息10000万次显然要快得多。
6、批量压缩:
在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于需要在广域网上的数据中心之间发送消息的数据流水线尤其如此。进行数据压缩会消耗少量的CPU资源,不过对于kafka而言,网络IO更应该需要考虑。
Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。
简单来说,分区中的所有副本统称为 AR (Assigned Replicas)。所有与leader副本保持一定程度同步的副本(包括leader副本在内)组成 ISR (In Sync Replicas)。 ISR 集合是 AR 集合的一个子集。消息会先发送到leader副本,然后follower副本才能从leader中拉取消息进行同步。 同步期间,follow副本相对于leader副本而言会有一定程度的滞后。 “一定程度同步“ 是指可忍受的滞后范围,这个范围可以通过参数进行配置。 于leader副本同步滞后过多的副本(不包括leader副本)将组成 OSR (Out-of-Sync Replied)由此可见,AR = ISR + OSR。 正常情况下,所有的follower副本都应该与leader 副本保持 一定程度的同步,即AR=ISR,OSR集合为空。
LEO:LEO是Log End Offset的缩写,它表示了当前日志文件中下一条待写入消息的offset。上图的LEO分别是8、6、9
LSO:Log Stable Offset。这是 Kafka 事务的概念。如果你没有使用到事务,那么这个 值不存在(其实也不是不存在,只是设置成一个无意义的值)。该值控制了事务型消费 者能够看到的消息范围。就是消费者只能消费到事务被提交的消息。
HW:分区ISR集合中的每个副本都会维护自身的LEO,而ISR集合中最小的LEO即为分区的HW, 对消费者而言只能消费HW之前的消息,HW之后的消息消费者是消费不到的。
LW:Low Watermark的缩写,俗称“低水位”,代表AR集合中最小的logStartOffset值(日志起始位移值)。上图中的LW都是从1开始的。
首先,Follower 发送 FetchRequest 请求给 Leader。 接着,Leader 会读取底层日志文件中的消 息数据,再更新它内存中的 Follower 副本的 LEO 值,更新为 FetchRequest 请求中的 fetchOffset 值。 最后,尝试更新分区高水位值(HW )。Follower 接收到 FETCH 响应之后,会把 消息写入到底层日志,接着更新 LEO 和 HW 值。
Kafaka的复制机制不是完全的同步复制,也不是单纯的异步复制,事实上, 同步复制要求所有能工作的Follower副本都复制完,这条消息才会被确认为成功提交, 这种复制方式影响了性能。而在异步复制的情况下, follower副本异步地从leader副本中复制数据, 数据只要被leader副本写入就被认为已经成功提交。在这种情况下,如果follower副本都没有复制完而落后于leader副本, 如果突然leader副本宕机,则会造成数据丢失。Kafka正是使用这种ISR的方式有效的权衡了数据可靠性与性能之间的关系。
Kafka会选择一个 broker 作为 “controller”节点。 controller 节点负责 检测 brokers 级别故障,并负责在 broker 故障的情况下更改这个故障 Broker 中的 partition 的 leadership 。 这种方式可以批量的通知主从关系的变化,使得对于拥有大量partition 的broker ,选举过程的代价更低并且速度更快。 如果 controller 节点挂了,其他存活的 broker 都可能成为新的 controller 节点。
Kafka的选举策略大致分一下 几种情况
这 4 类选举策略的大致思想是类似的,即从 AR 中挑选首个在 ISR 中的副本,作为新 Leader。
参考:
https://blog.csdn.net/sillyzhangye/article/details/86181345
https://blog.csdn.net/qq_26838315/article/details/106883256
https://www.cnblogs.com/18800105616a/p/13863254.html