Kafka剖析
1、总体概述
2、生产者流程:4步,2线程
3、消费者流程:5步,消费组与消费者,分区策略
4、broker之基本概念:
controller、topic、partition、replica,leader replica、follower replica、AR=ISR+OSR,HW与LEO
5、broker之日志存储格式
6、broker之日志索引
7、broker之日志清理,log.cleanup.policy=delete,compact,墓碑消息
8、零拷贝技术
9、延时任务之时间轮
总结:
1、生产者分区分配+消费者订阅主题的分区分配+创建主题broker中分区分配;
2、controller的选举+leader副本的选举;
===================================================
图1:
图2:
4步:
1、配置生产者客户端参数及创建相应的生产者实例
2、构建待发送的消息
3、发送消息
4、关闭生产者实例
主线程:拦截器 → 序列化器 →分区器 → 记录累加器
sender线程:
状态转换:
<分区, Deque<ProducerBatch>> →
<Node, List<ProducerBatch> →
<Node,Request>
InFlightRequests
元数据更新:leastLoadedNode
图3
===================================================
图4:
5步:
1、配置消费者客户端参数及创建相应的消费者实例
2、订阅主题
3、拉取消息并消费
4、提交消费位移,自动、手动
图5:
5、控制或关闭消费
分区策略:RangeAssignor,RoundRobinAssignor ,StickyAssignor
===================================================
一个集群只有一个控制器;
控制器是干嘛的?
监听broker、主题、分区相关的变化
更新集群元数据
启动并管理分区状态机和副本状态机
一个分区由一个leader副本,一个集群由n个leader副本;
一个分区的副本=leader副本+follower副本
topic和partition都是逻辑上的,topic-partition才是实际存在的目录;
AR=ISR+OSR,HW与LEO
图6:
===================================================
图7:
日志索引
先找LogSegment→ 再从索引文件中找索引项(二分法)→ 从索引项对应的物理位置顺序读取数据。
图8:
日志索引—找LogSegment用的跳跃表。
LogSegment大小?1G
索引文件大小?10M与实际大小
LogSegment的切分条件
日志删除:delete
基于时间、基于日志大小、基于日志起始偏移量(logStartOffset)
添加到deleteableSegments → 从跳表中移除 → “.deleted” → 由延迟任务来删除
图9:
日志清理:compact
清理线程 → 根据污浊率选择要清理的分区 →对分区中的LogSegment分组 →找可清理部分→SkimpyOffsetMap→清理到期的墓碑消息→更新cleaner checkpoint
图10:
图11:
===================================================
图12:
DMA就是主板上一个实际的芯片
DMA就好比一个中间代理者角色
图13:
图14:
kafka哪些场景使用了零拷贝技术?
时间轮与层级时间轮
图15:
DelayQueue → SystemTimer→TimerTaskList、TimerTask
DelayQueue的时间复杂度是
O
(
n
l
o
g
(
n
)
)
O(nlog(n))
O(nlog(n)),
DelayQueue中的元素要指定一个到期时间
DelayQueue里面的元素是什么?
为什么kafka中的定时器时间复杂度是
O
(
1
)
O(1)
O(1)
例1:
如果我们有1千万个TimerTask,最小时间单位是ms,最大一个任务要300年后执行
第1层:
tickMs=1ms,interval=
2
0
1
20^1
201=20ms
第2层:
tickMs=20ms,interval=
2
0
2
20^2
202=400ms
第3层:
tickMs=400ms,interval=
2
0
3
20^3
203=8000ms=8s
第4层:
tickMs=8000ms,interval=
2
0
4
20^4
204=160000ms=160s
第5层:
tickMs=160000ms,interval=
2
0
5
20^5
205=3200000ms=53m
第6层:
tickMs=3200000ms,interval=
2
0
6
20^6
206=160000ms=18h
第10层:
interval=
2
0
10
20^{10}
2010=160000ms=324年
降级?
例子:
当currentTime=16:29:07:0400时,第1个时间轮走了20圈,第2个时间轮走了1圈,第3个时间轮才走1个区间,指针指向1位置(最开始是指向0位置)
此时对于第3层第2个桶(即第2个TimerTaskList)中的TimerTask就到期了,那么会从DelayQueue中poll出队头,即第2个TimerTaskList,对第2个TimerTaskList里面的TimerTask进行重新add。这次add他们俩会被添加到第2层时间轮中,这就是时间轮的降级
什么时候指针更新?任何一层,只要有TimerTaskList到期就触发更新
if TimerTaskList的到期时间 >= currentTime + tickMs:
currentTime = timeMs - (timeMs % tickMs)
图16: