1. Flink+Kafka保证精确一次消费相关问题?
Fink的检查点和恢复机制和可以重置读位置的source连接器结合使用,比如kafka,可以保证应用程序不会丢失数据。尽管如此,应用程序可能会发出两次计算结果,因为从上一次检查点恢复的应用程序所计算的结果将会被重新发送一次(一些结果已经发送出去了,这时任务故障,然后从上一次检查点恢复,这些结果将被重新计算一次然后发送出去)。这个时候需要下层sink做到幂等性或者事务。
所以
· souce:使用执行ExactlyOnce的数据源,比如kafka等
· 内部使用FlinkKafakConsumer,并开启CheckPoint,偏移量会保存到StateBackend中,并且默认会将偏移量写入到topic中去,即_consumer_offsets Flink设置CheckepointingModel.EXACTLY_ONCE
· sink:
存储系统支持覆盖也即幂等性:如Redis,Hbase,ES等
存储系统不支持覆:需要支持事务(预写式日志或者两阶段提交),两阶段提交可参考Flink集成的kafka sink的实现。
2. 你们的Flink怎么提交的?使用的per-job模式吗?
模式 |
生命周期 |
资源隔离 |
优点 |
缺点 |
main方法 |
Session |
关闭会话,才会停止 |
共用JM和TM |
预先启动,启动作业不再启动。资源充分共享 |
资源隔离比较差,TM不容易扩展 |
在客户端执行 |
Per-job |
Job停止,集群停止 |
单个Job独享JM和TM |
充分隔离,资源根据job按需申请 |
job启动慢,每个job需要启动一个JobManager |
在客户端执行 |
Application |
当Application全部执行完,集群才会停止 |
Application使用一套JM和TM |
Client负载低,Application之间实现资源隔离,Application内实现资源共享 |
对per-job模式和session模式的优化部署模式(优点) |
在Cluster中 |
欢迎关注,一起学习
3. 了解过Flink的两阶段提交策略吗?讲讲详细过程。如果第一阶段宕机了会怎么办?第二阶段呢?
顾名思义,2PC将分布式事务分成了两个阶段,两个阶段分别为提交请求和提交。协调者根据参与者的响应来决定是否需要真正地执行事务
提交请求阶段
· 协调者向所有参与者发送prepare请求与事务内容,询问是否可以准备事务提交,并等待参与者的响应。
· 参与者执行事务中包含的操作,并记录undo日志(用于回滚)和redo日志(用于重放),但不真正提交。
· 参与者向协调者返回事务操作的执行结果,执行成功返回yes,否则返回no
提交执行阶段
分为成功与失败两种情况。
若所有参与者都返回yes,说明事务可以提交:
· 协调者向所有参与者发送commit请求。
· 参与者收到commit请求后,将事务真正地提交上去,并释放占用的事务资源,并向协调者返回ack。
· 协调者收到所有参与者的ack消息,事务成功完成。
若有参与者返回no或者超时未返回,说明事务中断,需要回滚:
· 协调者向所有参与者发送rollback请求
· 参与者收到rollback请求后,根据undo日志回滚到事务执行前的状态,释放占用的事务资源,并向协调者返回ack
· 协调者收到所有参与者的ack消息,事务回滚完成
对于Flink sink是kafka为例:
每当需要做checkpoint时,JobManager就在数据流中打入一个屏障(barrier),作为检查点的界限。屏障随着算子链向下游传递,每到达一个算子都会触发将状态快照写入状态后端(state BackEnd)的动作。当屏障到达Kafka sink后,触发preCommit(实际上是KafkaProducer.flush())方法刷写消息数据,但还未真正提交。接下来还是需要通过检查点来触发提交阶段。
第一阶段宕机,这个时候offset没有提交,重新启动会按offset继续消费和从状态中恢复状态值。
第二阶段宕机,分两种情况,在提交阶段后宕机,因为这个链路已经处理完,重新启动会按offset继续消费。在checkpint完成后宕机,还没有来得及触发提交阶段,这个时候可能会出现丢数据情况,这个时候有学者提出了三阶段提交。
4. 你是如何通过Flink实现uv的?
用户id刚好是数字,可以使用bitmap去重,简单原理是:把 user_id 作为 bit 的偏移量 offset,设置为 1 表示有访问,使用 1 MB的空间就可以存放 800 多万用户的一天访问计数情况。
val bloomFilter = new Bloom(1<<29)
// 先定义redis中存储位图的key
val storedBitMapKey = "xxxxx"
// 去重:判断当前userId的hash值对应的位图位置,是否为0
val userId = elements.last._2.toString
// 计算hash值,就对应着位图中的偏移量
val offset = bloomFilter.hash(userId, 61)
val isExist = jedis.getbit(storedBitMapKey, offset)
class Bloom(size: Long) extends Serializable{
private val cap = size // 默认cap应