参考 https://zhuanlan.zhihu.com/p/111304281
https://developer.aliyun.com/article/720793
flink官网文档链接 https://flink.apache.org/features/2018/03/01/end-to-end-exactly-once-apache-flink.html 有些不是很容易翻译,就不再这里赘述
Kafka两阶段提交指的是FlinkKafkaProducer,也就是sink阶段
1. Two-Phase Commit(2PC)分为 提交请求(投票)和提交(执行)两个阶段
2. Flink kafka的端到端exactly-once是基于kafka的事务来处理的
kafka的producer事务处理在原生api中如下
//初始化事务控制
producer.initTransactions();
try {
//开启事务
producer.beginTransaction();
producer.send(record1);
producer.send(record2);
//提交事务
producer.commitTransaction();
} catch (KafkaException e) {
//关闭事务
producer.abortTransaction();
}
3. 在Flink KafkaProducer中继承了 TwoPhaseCommitSinkFunction 来实现两阶段提交的功能(要弄清楚两阶段分别干了什么事)
该类下 有四个子类
protected abstract TXN beginTransaction() throws Exception;
protected abstract void preCommit(TXN transaction) throws Exception;
protected abstract void commit(TXN transaction);
protected abstract void abort(TXN transaction);
beginTransaction():开始一个事务,返回事务信息的句柄
preCommit():预提交(即提交请求)阶段的逻辑
commit():正式提交阶段的逻辑
abort():取消事务
preCommit预提交阶段:
@Override
protected void preCommit(KafkaTransactionState transaction) throws FlinkKafka011Exception {
switch (semantic) {
case EXACTLY_ONCE:
case AT_LEAST_ONCE:
flush(transaction);
break;
case NONE:
break;
default:
throw new UnsupportedOperationException("Not implemented semantic");
}
checkErroneous();
}
其中,flush()方法实际上是代理了KafkaProducer.flush()方法
preCommit()方法是在TwoPhaseCommitSinkFunction.snapshotState()中被使用
此阶段将快照轮次与当前事务记录到一个 Map 表示的待提交事务集合中,key 是当前快照轮次的 CheckpointId,value 是由 TransactionHolder 表示的事务对象。TransactionHolder 对象内部记录了 transactionalId、producerId、epoch 以及 Kafka 客户端 kafkaProducer 的引用
持久化当前事务处理状态,也就是将当前处理的事务详情存入状态后端
commit提交阶段:
@Override
protected void commit(KafkaTransactionState transaction) {
if (transaction.isTransactional()) {
try {
transaction.producer.commitTransaction();
} finally {
recycleTransactionalProducer(transaction.producer);
}
}
}
commit()方法实际上是代理了KafkaProducer.commitTransaction()方法,正式向Kafka提交事务
该方法的调用点位于TwoPhaseCommitSinkFunction.notifyCheckpointComplete()方法中,当所有检查点都成功完成之后,会回调这个方法。
只有在所有检查点都成功完成这个前提下,写入才会成功。一旦有检查点失败,notifyCheckpointComplete()方法就不会执行。
如果重试也不成功的话,最终会调用abort()方法回滚事务
未commit()的数据在消费时默认也是可以消费到的,consumer消费者参数中
public static final String ISOLATION_LEVEL_CONFIG = "isolation.level";
//控制怎么读取事务性的消息,如果设置为 read_committed, consumer.poll() 只会等待事务性消息被提交后才会继续消费
这个参数的默认值为 read_uncommitted