我们知道,端到端的状态一致性的实现,需要每一个组件都实现,对于
Flink +
Kafka
的数据管道系统(
Kafka
进、
Kafka
出)而言,各组件怎样保证
exactly-once
语义呢?
⚫
内部 —— 利用
checkpoint
机制,把状态存盘,发生故障的时候可以恢复,
保证内部的状态一致性
⚫
source
——
kafka consumer
作为
source
,可以将偏移量保存下来,如果后
续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新消费数据,
保证一致性
⚫
sink
——
kafka producer
作为
sink
,采用两阶段提交
sink
,需要实现一个
TwoPhaseCommitSinkFunction
内部的
checkpoint
机制我们已经有了了解,那
source
和
sink
具体又是怎样运行
的呢?接下来我们逐步做一个分析。
我们知道
Flink
由
JobManager
协调各个
TaskManager
进行
checkpoint
存储,
checkpoint
保存在
StateBackend
中,默认
StateBackend
是内存级的,也可以改为文
件级的进行持久化保存
![](https://img-blog.csdnimg.cn/8e34089fcd4a428bb193621571b8edd6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16)
当
checkpoint
启动时,
JobManager
会将检查点分界线(
barrier
)注入数据流;
barrier
会在算子间传递下去。
![](https://img-blog.csdnimg.cn/23cf86b0d86b4c97a1ad7489086cb1f4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16)
每个算子会对当前的状态做个快照,保存到状态后端。对于
source
任务而言,
就会把当前的
offset
作为状态保存起来。下次从
checkpoint
恢复时,
source
任务可
以重新提交偏移量,从上次保存的位置开始重新消费数据。
![](https://img-blog.csdnimg.cn/94340bbfb6294ad4a4e0e226c996a8b1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16)
每个内部的
transform
任务遇到
barrier
时,都会把状态存到
checkpoint
里。
sink
任务首先把数据写入外部
kafka
,这些数据都属于预提交的事务(还不能
被消费);当遇到
barrier
时,把状态保存到状态后端,并开启新的预提交事务。
![](https://img-blog.csdnimg.cn/81934680523948e1af5ded564dd1bba6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16)
当所有算子任务的快照完成,也就是这次的
checkpoint
完成时,
JobManager
会
向所有任务发通知,确认这次
checkpoint
完成。
当
sink
任务收到确认通知,就会正式提交之前的事务,
kafka
中未确认的数据
就改为“已确认”,数据就真正可以被消费了。
![](https://img-blog.csdnimg.cn/11fe7b6a45fc4ac094f4eb52ae9d2e6e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16)
所以我们看到,执行过程实际上是一个两段式提交,每个算子执行完成,会进
行“预提交”,直到执行完
sink
操作,会发起“确认提交”,如果执行失败,预提
交会放弃掉。
具体的两阶段提交步骤总结如下:
⚫
第一条数据来了之后,开启一个
kafka
的事务(
transaction
),正常写入
kafka
分区日志但标记为未提交,这就是“预提交”
⚫
jobmanager
触发
checkpoint
操作,
barrier
从
source
开始向下传递,遇到
barrier
的算子将状态存入状态后端,并通知
jobmanager
⚫
sink
连接器收到
barrier
,保存当前状态,存入
checkpoint
,通知
jobmanager
,并开启下一阶段的事务,用于提交下个检查点的数据
⚫
jobmanager
收到所有任务的通知,发出确认信息,表示
checkpoint
完成
⚫
sink
任务收到
jobmanager
的确认信息,正式提交这段时间的数据
⚫
外部
kafka
关闭事务,提交的数据可以正常消费了。
所以我们也可以看到,如果宕机需要通过
StateBackend
进行恢复,只能恢复所
有确认提交的操作
4
选择一个状态后端
(state backend)
•
MemoryStateBackend
内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储
在
TaskManager
的
JVM
堆上;而将
checkpoint
存储在
JobManager
的内存中。
•
FsStateBackend
将
checkpoint
存到远程的持久化文件系统(
FileSystem
)上。而对于本地状
态,跟
MemoryStateBackend
一样,也会存在
TaskManager
的
JVM
堆上。
•
RocksDBStateBackend
将所有状态序列化后,存入本地的
RocksDB
中存储。
注意:
RocksDB
的支持并不直接包含在
flink
中,需要引入依赖
![](https://img-blog.csdnimg.cn/11b3530817ac4cfeb1c31eaba974f63c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc29uZ19xdWFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16)
设置状态后端为 FsStateBackend,并配置检查点和重启策略: