flink 通过内部依赖checkpoint 并且可以通过设置其参数exactly-once 实现其内部的一致性。但要实现其端到端的一致性,还必须保证
1、source 外部数据源可重设数据的读取位置
2、sink端 需要保证数据从故障恢复时,数据不会重复写入外部系统(或者可以逻辑实现写入多次,但只有一次生效的数据sink端
)
幂等操作:
一个操作,可以重复执行多次,但只导致一次结果更改,豁免重复操作执行就不起作用了,他的瑕疵 (在系统恢复的过程中,如果这段时间内多个更新或者插入导致状态不一致,但当数据追上就可以了)
(逻辑与、逻辑或等)具体理解参照自己以前写的文章。
事务写入:
事务应该具有四个属性:原子性、一致性、隔离性、持久性等。其具体的实现方式有两种
(1)、预写日志
简单易于实现,由于数据提前在状态后端中做了缓存,所以无论什么sink系统,都能用这种方式一批搞定,DataStream API提供了一个模板类:GenericWriteAheadSink,来实现这种事务性sink;
缺点:
1)、sink系统没说他支持事务。有可能出现一部分写入集群了。一部分没有写进去(如果实表,再写一次就写重复了)
2)、checkpoint做完了sink才去真正的写入(但其实得等sink都写完checkpoint才能生效,所以WAL这个机制jobmanager确定它写完还不算真正写完,还得有一个外部系统已经确认 完成的checkpoint)
(2)、两阶段提交。 flink 真正实现exactle-once
对于每个checkpoint,sink 任务会启动一个事务,并将接下来所有接收的数据添加到事务中,然后将这些数据写入外部sink系统,但不提交他们(这里是预提交)。当checkpoint完成时的通知,它才正式提交事务,实现结果的真正写入;这种方式真正实现了exactly-once,它需要一个提供事务支持的外部sink系统,Flink提供了其具体实现(TwoPhaseCommitSinkFunction接口)
1、外部sink系统必须事务支持,或者sink任务必须能够模拟外部系统上的事务;
2、在checkpoint的间隔期间里,必须能够开启一个事务,并接受数据写入。
3、在收到checkpoint完成通知之前,事务必须是“等待提交”的状态,在故障恢复的情况线,这可能需要一些时间。如果个时候sink系统关闭事务(例如超时了),那么未提交的数据就会丢失;
4、四年任务必选能够在进程失败后恢复事务
5、提交事务必须是幂等操作;
flink 和kafka 端到端一致性(kafka(source+flink+kafka(sink)))
1、内部 – 利用checkpoint机制,把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性
2、source – kafka consumer作为source,可以将偏移量保存下来,如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新消费数据,保证一致性;
kafka 0.8 和kafka 0.11 之后 通过以下配置将偏移量保存,恢复时候重新消费
kafka.setStartFromLatest();
kafka.setCommitOffsetsOnCheckpoints(false);
kafka 0.9 和kafka0.10 未验证是否支持这两个参数(todo)
3、sink FlinkkafkaProducer作为Sink,采用两阶段提交的sink,由下图可以看出flink 0.11 已经默认继承了TwoPhaseCommitSinkFunction
但我们需要在参数种传入指定语义,它默认时还是at-least-once
此外我们还需要进行一些producer的容错配置:
(1)除了启用Flink的检查点之外,还可以通过将适当的semantic参数传递给FlinkKafkaProducer011(FlinkKafkaProducer对于Kafka> = 1.0.0版本)
(2)来选择三种不同的操作模式
1)、Semantic.NONE 代表at-mostly-once语义
2)、Semantic.AT_LEAST_ONCE(Flink默认设置
3)、Semantic.EXACTLY_ONCE 使用Kafka事务提供一次精确的语义,每当您使用事务写入Kafka时
(3)、请不要忘记消费kafka记录任何应用程序设置所需的设置isolation.leva(read_committed 或者read_uncommitted-后者是默认)
read_committed,只是读取已经提交的数据。
应用;
Semantic.EXACTLY_ONCE依赖与下游系统能支持事务操作.以0.11kafka为例.
transaction.max.timeout.ms 最大超市时长,默认15分钟,如果需要用exactly语义,需要增加这个值。(因为它小于transaction.timeout.ms )
isolation.level 如果需要用到exactly语义,需要在下级consumerConfig中设置read-commited [read-uncommited(默认值)]
transaction.timeout.ms 默认为1hour
其参数对应关系为 和一些报错问题
checkpoint间隔
参考:https://www.cnblogs.com/createweb/p/11971846.html
注意:
1、Semantic.EXACTLY_ONCE 模式每个FlinkKafkaProducer011实例使用一个固定大小的KafkaProducers池。每个检查点使用这些生产者中的每一个。如果并发检查点的数量超过池大小,FlinkKafkaProducer011 将引发异常,并使整个应用程序失败。请相应地配置最大池大小和最大并发检查点数。
2、Semantic.EXACTLY_ONCE采取所有可能的措施,不要留下任何挥之不去的数据,否则这将有碍于消费者更多地阅读Kafka主题。但是,如果flink应用程序在第一个检查点之前失败,则在重新启动此类应用程序后,系统种将没有有关先前池大小信息,因此,在第一个检查点完成前按比例缩小Flink应用程序的FlinkKafkaProducer011.SAFE_SCALE_DOWN_FACTOR
//1。设置最大允许的并行checkpoint数,防止超过producer池的个数发生异常
env.getCheckpointConfig.setMaxConcurrentCheckpoints(5)
//2。设置producer的ack传输配置
// 设置超市时长,默认15分钟,建议1个小时以上
producerConfig.put(ProducerConfig.ACKS_CONFIG, 1)
producerConfig.put(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG, 15000)
//3。在下一个kafka consumer的配置文件,或者代码中设置ISOLATION_LEVEL_CONFIG-read-commited
//Note:必须在下一个consumer中指定,当前指定是没用用的
kafkaonfigs.setProperty(ConsumerConfig.ISOLATION_LEVEL_CONFIG,"read_commited")
完整应用代码:
package com.shufang.flink.connectors
import java.util.Properties
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer.Semantic
import org.apache.flink.streaming.connectors.kafka._
import org.apache.flink.streaming.util.serialization.KeyedSerializationSchemaWrapper
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.clients.producer.ProducerConfig
import org.apache.kafka.common.serialization.StringDeserializer
object KafkaSource01 {
def main(args: Array[String]): Unit = {
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//这是checkpoint的超时时间
//env.getCheckpointConfig.setCheckpointTimeout()
//设置最大并行的chekpoint
env.getCheckpointConfig.setMaxConcurrentCheckpoints(5)
env.getCheckpointConfig.setCheckpointInterval(1000) //增加checkpoint的中间时长,保证可靠性
/**
* 为了保证数据的一致性,我们开启Flink的checkpoint一致性检查点机制,保证容错
*/
env.enableCheckpointing(60000)
/**
* 从kafka获取数据,一定要记得添加checkpoint,能保证offset的状态可以重置,从数据源保证数据的一致性
* 保证kafka代理的offset与checkpoint备份中保持状态一致
*/
val kafkaonfigs = new Properties()
//指定kafka的启动集群
kafkaonfigs.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092")
//指定消费者组
kafkaonfigs.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "flinkConsumer")
//指定key的反序列化类型
kafkaonfigs.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer].getName)
//指定value的反序列化类型
kafkaonfigs.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, classOf[StringDeserializer].getName)
//指定自动消费offset的起点配置
// kafkaonfigs.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest")
/**
* 自定义kafkaConsumer,同时可以指定从哪里开始消费
* 开启了Flink的检查点之后,我们还要开启kafka-offset的检查点,通过kafkaConsumer.setCommitOffsetsOnCheckpoints(true)开启,
* 一旦这个检查点开启,那么之前配置的 auto-commit-enable = true的配置就会自动失效
*/
val kafkaConsumer = new FlinkKafkaConsumer[String](
"console-topic",
new SimpleStringSchema(), // 这个schema是将kafka的数据应设成Flink中的String类型
kafkaonfigs
)
// 开启kafka-offset检查点状态保存机制
kafkaConsumer.setCommitOffsetsOnCheckpoints(true)
// kafkaConsumer.setStartFromEarliest()//
// kafkaConsumer.setStartFromTimestamp(1010003794)
// kafkaConsumer.setStartFromLatest()
// kafkaConsumer.setStartFromSpecificOffsets(Map[KafkaTopicPartition,Long]()
// 添加source数据源
val kafkaStream: DataStream[String] = env.addSource(kafkaConsumer)
kafkaStream.print()
val sinkStream: DataStream[String] = kafkaStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[String](Time.seconds(5)) {
override def extractTimestamp(element: String): Long = {
element.split(",")(1).toLong
}
})
/**
* 通过FlinkkafkaProduccer API将stream的数据写入到kafka的'sink-topic'中
*/
// val brokerList = "localhost:9092"
val topic = "sink-topic"
val producerConfig = new Properties()
producerConfig.put(ProducerConfig.ACKS_CONFIG, new Integer(1)) // 设置producer的ack传输配置
producerConfig.put(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG, Time.hours(2)) //设置超市时长,默认1小时,建议1个小时以上
/**
* 自定义producer,可以通过不同的构造器创建
*/
val producer: FlinkKafkaProducer[String] = new FlinkKafkaProducer[String](
topic,
new KeyedSerializationSchemaWrapper[String](SimpleStringSchema),
producerConfig,
Semantic.EXACTLY_ONCE
)
// FlinkKafkaProducer.SAFE_SCALE_DOWN_FACTOR
/** *****************************************************************************************************************
* * 出了要开启flink的checkpoint功能,同时还要设置相关配置功能。
* * 因在0.9或者0.10,默认的FlinkKafkaProducer只能保证at-least-once语义,假如需要满足at-least-once语义,我们还需要设置
* * setLogFailuresOnly(boolean) 默认false
* * setFlushOnCheckpoint(boolean) 默认true
* * come from 官网 below:
* * Besides enabling Flink’s checkpointing,you should also configure the setter methods setLogFailuresOnly(boolean)
* * and setFlushOnCheckpoint(boolean) appropriately.
* ******************************************************************************************************************/
producer.setLogFailuresOnly(false) //默认是false
/**
* 除了启用Flink的检查点之外,还可以通过将适当的semantic参数传递给FlinkKafkaProducer011(FlinkKafkaProducer对于Kafka> = 1.0.0版本)
* 来选择三种不同的操作模式:
* Semantic.NONE 代表at-mostly-once语义
* Semantic.AT_LEAST_ONCE(Flink默认设置)
* Semantic.EXACTLY_ONCE:使用Kafka事务提供一次精确的语义,每当您使用事务写入Kafka时,
* 请不要忘记为使用Kafka记录的任何应用程序设置所需的设置isolation.level(read_committed 或read_uncommitted-后者是默认值)
*/
sinkStream.addSink(producer)
env.execute("kafka source & sink")
}
}
部分参考:https://blog.csdn.net/shufangreal/article/details/104737652?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param