原文链接:
本文开头附:Flink 学习路线系列 ^ _ ^
Flink 整合 Kafka 基本步骤,请参考:Flink 基础整合 Kafka。本文仅用来介绍 Flink 整合 Kafka 实现 Exactly-Once。
1.什么是Exactly-Once
恰好处理一次的意思。不管在处理的时候是否有异常发生,计算的结果都一样。即使在发现机器或者软件故障时,都不会出现数据丢失以及重复处理的情况。(就是每条数据只会被处理一次)
Flink 中哪些 Source 、Sink支持 Exactly-Once 呢,Flink官方文档(链接)为我们做了描述。如下图所示:
Source:
Sink:
我们发现很多都是 at least once(至少一次),我们可以基于幂等操作(幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同),只要是偏移量记录正确,如果你记录了2次数据,可以将原来的数据覆盖掉,以此来实现 exactly-once 操作
2.场景(Demo)
Flink 实时读取 Kafka 中的数据,并保证 Exaclty-Once。即:当任务出现异常,触发重启策略任务被重启后,对已经消费过的消息不进行重复消费。
2.1 代码
/**
* TODO Flink整合 Kafka,实现 Exactly-Once
*
* @author liuzebiao
* @Date 2020-2-15 9:53
*/
public class RestartStrategyDemo { public static void main(String[] args) throws Exception {
/**1.创建流运行环境**/
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); /**请注意此处:**/
//1.只有开启了CheckPointing,才会有重启策略
env.enableCheckpointing(5000);
//2.默认的重启策略是:固定延迟无限重启
//此处设置重启策略为:出现异常重启3次,隔5秒一次
env.getConfig().setRestartStrategy(RestartStrategies.fixedDelayRestart(2, Time.seconds(2)));
//系统异常退出或人为 Cancel 掉,不删除checkpoint数据
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
//设置Checkpoint模式(与Kafka整合,一定要设置Checkpoint模式为Exactly_Once)
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); /**2.Source:读取 Kafka 中的消息**/
//Kafka props
Properties properties = new Properties();
//指定Kafka的Broker地址
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.204.210:9092,192.168.204.211:9092,192.168.204.212:9092");
//指定组ID
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "testGroup");
//如果没有记录偏移量,第一次从最开始消费
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
//Kafka的消费者,不自动提交偏移量
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer("testTopic", new SimpleStringSchema(), properties);
//Checkpoint成功后,还要向Kafka特殊的topic中写偏移量(此处不建议改为false )
//设置为false后,则不会向特殊topic中写偏移量。
//kafkaSource.setCommitOffsetsOnCheckpoints(false);
//通过addSource()方式,创建 Kafka DataStream
DataStreamSource<String> kafkaDataStream = env.addSource(kafkaSource); /**3.Transformation过程**/
SingleOutputStreamOperator<Tuple2<String, Integer>> streamOperator = kafkaDataStream.map(str -> Tuple2.of(str, 1)).returns(Types.TUPLE(Types.STRING, Types.INT)); /**此部分读取Socket数据,只是用来人为出现异常,触发重启策略。验证重启后是否会再次去读之前已读过的数据(Exactly-Once)*/
/*************** start **************/
DataStreamSource<String> socketTextStream = env.socketTextStream("localhost", 8888); SingleOutputStreamOperator<String> streamOperator1 = socketTextStream.map(new MapFunction<String, String>() {
@Override
public String map(String word) throws Exception {
if ("error".equals(word)) {
throw new RuntimeException("Throw Exception");
}
return word;
}
});
/************* end **************/ //对元组 Tuple2 分组求和
SingleOutputStreamOperator<Tuple2<String, Integer>> sum = streamOperator.keyBy(0).sum(1); /**4.Sink过程**/
sum.print(); /**5.任务执行**/
env.execute("RestartStrategyDemo");
}
}
2.2 测试
2.2.1 Kafka创建Topic
创建 Topic 命令如下:
bin/kafka-topics.sh --create --zookeeper 192.168.204.210:2181,192.168.204.211:2181,192.168.204.212:2181 --replication-factor 1 --partitions 3 --topic testTopic
Topic创建成功:
2.2.2 写入数据到 Kafka
我们使用命令的方式,向Kafka集群中的某一台写入消息(本文向192.168.204.210节点 Kafka 写入数据)。命令如下:
bin/kafka-console-producer.sh --broker-list 192.168.204.210:9092 --topic testTopic
写入部分数据:
2.2.2 测试动图
2.2.3 测试结果解析
当输入 3个AAA 、1个BBB,返回计算结果(AAA,3)、(BBB,1);
此时在 socket 端口号 8888 中输入“error”,人为原因导致实时任务异常。通过动图可以看到任务已经出发重启策略开始重启;
在任务重启前,动图中我已经清空了控制台日志。当任务重启完成,我们搜索 AAA,发现并没有搜到内容,说明之前已经消费的消息没有被重复消费,已经实现了 Exactly-Once;
当再次 1个AAA 、1个BBB时,任务接着异常前的结果继续累加,返回计算结果(AAA,4)、(BBB,2)。
2.3 Flink整合Kafka发现的其他问题
1.kafkaSource.setCommitOffsetsOnCheckpoints(boolean);方法,是用来干什么的?
官方文档有介绍,你可参考:Flink官方文档。代码中不建议将kafkaSource.setCommitOffsetsOnCheckpoints(boolean);方法设置为 false。此处,我们对该方法来做一个书面介绍:
①你如果禁用CheckPointing,则Flink Kafka Consumer依赖于内部使用的Kafka客户端的自动定期偏移量提交功能。该偏移量会被记录在 Kafka 中的 _consumer_offsets 这个特殊记录偏移量的 Topic 中。
②你如果启用CheckPointing,偏移量则会被记录在 StateBackend 中。该方法kafkaSource.setCommitOffsetsOnCheckpoints(boolean);设置为 ture 时,偏移量会在 StateBackend 和 Kafka 中的 _consumer_offsets Topic 中都会记录一份;设置为 false 时,偏移量只会在 StateBackend 中的 存储一份。
附:查看 Kafka 中 _consumer_offsets Topic 的偏移量命令:
/usr/local/env/kafka/bin/kafka-console-consumer.sh --topic __consumer_offsets --bootstrap-server 192.168.204.210:9092,192.168.204.211:9092,192.168.204.212:9092 --formatter “kafka.coordinator.group.GroupMetadataManager$OffsetsMessageFormatter” --consumer.config /usr/local/env/kafka/config/consumer.properties --from-beginning | grep 你的Topic名称
执行代码如下图所示:
2. 动图中,Flink任务重启时,并未指定 savePoint 路径,为什么还能够恢复数据?
①如果任务重启时,指定savePoint路径(Checkpoint路径),它则会从指定的savePoint路径恢复数据
②如果不指定 savePoint 路径,任务会从 Kafka 中的_consumer_offsets这个 topic 中,查看有没有相同group.id 的 topic 的偏移量,如果有的话就会接着之前写入的偏移量来读。
这就是Flink任务重启时,并未指定 savePoint 路径,还能够恢复数据的原因↑↑↑
Flink 整合 Kafka 实现 Exactly-Once,介绍到此为止
用空常来坐坐