基于Flink和kafka整合,程序异常时自动重启恢复,来实现exactly_once(Redis幂等性)

文章目录

    • 一、解读
        • 1、思考:
        • 2、整体架构图
    • 二、代码详解
        • 1、checkPoint部分
            • 设定在存储HDFS上,需注意:
        • 2、FlinkKafka Source部分
            • 注意:
        • 3、数据处理部分
        • 4、Sink到Redis部分

一、解读

基于flink和kafka进行整合,当程序出现异常时自动重启数据恢复。

  • 实现exactly_once,就要做checkPoint,纪录偏移量到Operator State,累加的次数计入keyedState,将checkPoint数据计入StateBackEnd

1、思考:

当做checkPoint时程序挂掉,然后程序自动重启,那距上次ck时,这段时间读的数据,岂不是重复读取重复计算了吗?

基于Flink和kafka整合,程序异常时自动重启恢复,来实现exactly_once(Redis幂等性)_第1张图片

  • 程序即使数据重读,那么我们把重复读的数据,sum后存入Redis,根据Redis幂等性的特性,将数据覆盖,从而实现了at_least_once

2、整体架构图

基于Flink和kafka整合,程序异常时自动重启恢复,来实现exactly_once(Redis幂等性)_第2张图片

二、代码详解

1、checkPoint部分

  • ① 开启checkPoint,生产环境中一般是分钟级别的
  • ② 设置重启策略,重启次数
  • ③ 设置chenkPoint的中间数据存储的位置
  • ④ 设置checkPoint的形式:AT_LEAST_ONCE、EXACTLY_ONCE
  • ⑤ 配置清空策略,防止cancel掉job后,checkpoint的数据不会被删除掉
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.enableCheckpointing(30000);
        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 5000));
        env.setStateBackend(new FsStateBackend("hdfs://linux01:9000/it"));     
        env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE);
        
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig
                .ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
设定在存储HDFS上,需注意:
  • 需要将 flink-shaded-hadoop-2-uber-2.8.3-10.0.jar 放入Flink集群每个机器的 flink…/lib路径下
  • 下载地址:https://flink.apache.org/downloads.html

2、FlinkKafka Source部分

可参考官网:https://ci.apache.org/projects/flink/flink-docs-release-1.10/dev/connectors/kafka.html

  • ① pom文件引入kafka依赖
<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-kafka_2.11artifactId>
  <version>1.10.0version>
dependency>
  • ② kafka配置文件的配置(组名、topic名、auto.offset.reset参数详解可参阅我的另一篇博客)
  • ③ 在checkpoint时,是否将kafka的偏移量保存到kafka特殊的topic中,默认是true
        // 二、配置kafka的参数
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "linux01:9092,linux02:9092,linux03:9092");//kafka集群机器
        properties.setProperty("group.id", "g001");
        properties.setProperty("auto.offset.reset", "earliest");
        //不自动提交偏移量,如果不设置,默认为true
        // properties.setProperty("enable.auto.commit", "false");
        
        FlinkKafkaConsumer<String> flinkKafkaConsumer = new FlinkKafkaConsumer<>("wordCount", new SimpleStringSchema(), properties);

		// ③
        flinkKafkaConsumer.setCommitOffsetsOnCheckpoints(false);
        
        DataStreamSource<String> lines = env.addSource(flinkKafkaConsumer);
        
注意:

得到的Kafka Source 是可并行的、可checkPoint的source,因为查看源码我们可以清晰的得到 →

  • FlinkKafkaConsumer继承了RichParallelSourceFunction,实现了CheckpointedFunction接口
  • 当开启checkPoint时,Kafka Source 就会做ck,来记录消费数据的偏移量Operator State 至StateBackEnd,该数据保存在高可用的HDFS上。

3、数据处理部分

  • 使用Lambda表达式时,要使用returns指定返回的数据类型
  • 调用sum算子之后,会做checkPoint, 将聚合结果keyedState保存至StateBackEnd(位于hdfs上)
		// lambda表达式的使用
        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = lines.flatMap((String line, Collector<Tuple2<String, Integer>> out) -> {
            Arrays.stream(line.split(" ")).forEach(w -> out.collect(Tuple2.of(w, 1)));
        })
          .returns(Types.TUPLE(Types.STRING, Types.INT));

        SingleOutputStreamOperator<Tuple2<String, Integer>> sum = wordAndOne.keyBy(0).sum(1);


4、Sink到Redis部分

可参考官网:https://bahir.apache.org/docs/flink/current/flink-streaming-redis/

将sum聚合之后的数据,通过调用sink,将数据写出到Redis数据库中

  • ① 添加Redis的依赖
<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-redis_2.11artifactId>
  <version>1.1.5version>
dependency>
  • ② 自定义sink的Redis类,实现RedisMapper接口
  • ③ 创建Jedis连接的配置信息(redis所在机器IP、使用哪个库、连接密码)
	// 以内部类的形式或者单独一个类
    public static class RedisWordCountMapper implements RedisMapper<Tuple2<String, Integer>> {

        //指定写入Redis中的方法和最外面的大key的名称
        @Override
        public RedisCommandDescription getCommandDescription() {
            return new RedisCommandDescription(RedisCommand.HSET, "wc");
        }

        @Override
        public String getKeyFromData(Tuple2<String, Integer> stringIntegerTuple2) {
            return stringIntegerTuple2.f0;//将数据中的哪个字段作为key写入
        }

        @Override
        public String getValueFromData(Tuple2<String, Integer> stringIntegerTuple2) {
            return stringIntegerTuple2.f1.toString();//将数据中的哪个字段作为value写入
        }
    }
        //创建Jedis连接的配置信息
        FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder()
                .setHost("192.168.23.103")//redis所在机器的地址
                .setDatabase(5)//选择哪个数据库
                .setPassword("123456")
                .build();

        sum.addSink(new RedisSink<>(conf, new RedisWordCountMapper()));
        env.execute("FlinkKafka");
        

你可能感兴趣的:(Flink实时篇)