Kafka原理性知识点集合

单独开一章记录下,持续追加,不涉及代码,直击原理,最近在看Kafka3去ZK的底层实现,这一章也算是温故而知新

KRaft协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Kafka3目前大家听到最大的噱头就是"脱离ZK依赖"
ZK在Kafka中的作用
Controller选举
Broker注册
TopicPartition注册
Leader选举
Consumer/Producer元数据管理
负载均衡

看Kraft之前可以了解一下什么是Raft共识协议
http://thesecretlivesofdata.com/raft/

很直白就是,我们如何保证Controller Quorum节点数为奇数,最多容忍(n/2-1)个节点失败
并且只有一个节点为Active Controller

Raft只有两种RPC消息,AppendEntries/RequestVote,交互方式为Push

KRaft交互模式为Pull,RPC消息:
Vote,选举的选票消息,Candidate发送
BeginQuorumEpoch,新Leader当选时发送,告知其他节点Leader信息
EndQuorumEpoch,当前Leader退位时发送,触发重新选举
Fetch,复制Leader日志,由Follower/Observer发送
Raft协议AppendEntries是Leader将日志推送给Follower
KRaft协议Fetch是从Leader拉取日志,Fetch也可以作为Follower对Leader的活性探测

相关配置
quorum.voters: 连接映射,格式为{broker-id}@{broker-host):{broker-port}
quorum.fetch.timeout.ms: 在开始新的选举之前没有从当前领导者成功获取的最长时间
quorum.election.timeout.ms: 在重试新选举之前,候选人状态期间未获得多数选票的最长时间
quorum.election.backoff.max.ms: 选举超时后,在触发新选举之前的最大指数退避时间(基于重试次数)
quorum.request.timeout.ms: 挂起请求被视为失败并断开连接之前的最长时间
quorum.retry.backoff.ms: 请求重试之间的初始延迟
quorum.retry.backoff.max.ms: 请求之间的最大延迟
broker.id: 现有的BrokerId配置将用作Raft Quorum中的VoterId

更多的内容可以参考[KIP-595]
https://cwiki.apache.org/confluence/display/KAFKA/KIP-595%3A+A+Raft+Protocol+for+the+Metadata+Quorum

Kafka分区以及副本分配算法

1
2
3
4
5
6
7
8
9
创建一个Topic,这个Topic下的分区以及副本是按照什么规律分布在各个节点上
4台Broker[0,1,2,3]

创建一个Topic,分区数是4,1个副本
P-0,P-1,P-2,P-3
分配规则:
主Partition,按照BrokerID依次分配
副本,在n个Broker中,第x(从0开始)个Partition的第y(从1开始)个副本分配在(x+y) mod n,结果s(从0开始)代表的Broker上
P-0的副本(0+1) mod 4 = 1

PageCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
=-=Kafka快的原因,耳朵都起茧了
顺序读取,稀疏索引,页缓存,0拷贝

Linux 2.4前
PageCache:缓存文件的页数据
BufferCache:缓存块设备(磁盘)的块数据

Linux 2.4后
两者近似融合,都统称为PageCache

PageCache的目的
加速数据I/O
写数据先写到缓存,将写入的页标记为dirty,再向外部存储flush(缓存写机制的write-back)
读数据先读取缓存,未命中再去外部存储读取,读取之后加入缓存,在使用free命令时可以经常性看到free内存很少,buff/cache很大

Kafka使用PageCache
为啥不用JVM
JVM数据的存储对应着对象建立,浪费空间
JVM管理缓存,就会导致GC,过多过大的GC会影响GC效率,降低吞吐率
Producer
产生消息时,pwrite()系统调用[JavaNIO中是FileChannel.write()]按偏移量写入数据到PageCache
Consumer
消费消息时,sendfile()系统调用[JavaNIO中是FileChannel.transferTo()]将数据从PageCache传输到Broker的Socket Buffer中,再通过网络传输,这也是零拷贝

读写空中接力(inflight >v>有没有很熟悉)
当Producer和生产速率和Consumer的消费速率相差不大,几乎只靠对PageCache的读写完成整个生产消费过程,磁盘访问非常小

分区消费分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
RangeAssignor(默认)
Consumer总数x
Partition总数y
区间值i = y / x
余数值j = y % x
按顺序,每个Consumer顺序分配j+1个Partition,不能均分后面的Consumer分配j个Partition
8个Partition,3个Consumer,i=2,j=2
c0 = [p0,p1,p2]
c1 = [p3,p4,p5]
c2 = [p6,p7]
这种分配在多topic中,且partition数不一致情况下,排在前面的Consumer会分配到大量的partition,造成负载倾斜

RoundRobinAssignor
按照轮询进行分配
8个Partition,3个Consumer,i=2,j=2
c0 = [p0,p3,p6]
c1 = [p1,p4,p7]
c2 = [p2,p5]
即便多个topic,partition数量不一致,也可以保证分配基本平均
但是当Consumer订阅不同的Topic时,同样会产生倾斜

StickyAssignor(0.11)
Partition分配尽量平均,重分配时,尽量保留上一次的分配,尽量少将已经分配了的Partition分配给其他Consumer
如果发生冲突,则优先保证平均分配
详细代码去看
org.apache.kafka.clients.consumer.internals.AbstractStickyAssignor;

自定义分配策略
自定义实现PartitionAssignor或AbstractPartitionAssignor
重写assign方法