大数据开发Flink高级进阶之KafkaConnector(第五十篇)

407 阅读2分钟

一、并行度

  1. 一个Flink任务由多个组件组成(DataSource、Transformation、DataSink)
  2. 一个组件由多个并行的实例(线程)来执行,一个组件的并行实例(线程)数目就被称为该组件的并行度
1.1、并行度的设置
  1. Operator Level 算子层面

    setParallelism(5)

  2. Execution Environment Level 执行环境层面

    env.setParallelism(3)

  3. Client Level 客户端层面

    在客户端提交JOB时设定,通过-p参数执行并行度

  4. System Level 系统层面

    在系统层面可以通过设置flink-conf.yaml文件中的parallelism.default属性来指定所有执行环境的默认并行度

二、Flink之KafkaConnector

Java项目引入下面的依赖

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka</artifactId>
    <version>1.16.0</version>
</dependency>
2.1、Kafka Consumer消费策略设置
  1. setStartFromGroupOffsets 默认消费策略
  2. setStartFromEarliest 从最早的开始消费 或者setStartFromLatest 从最新的开始消费
  3. setStartFromTimestamp 按照时间戳往后面开始消费
public class FlinkWithKafka {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // flink 1.3版本以后,不推荐下面的写法,推荐使用KafkaSource
​
        // FlinkKafkaConsumer<String> stringFlinkKafkaConsumer = new FlinkKafkaConsumer<>("", new SimpleStringSchema(), properties);
        // 默认策略,读取group id对应保存的offset开始消费数据,读取不到根据kafka中auto.offset.reset参数的值开始消费数据
        // stringFlinkKafkaConsumer.setStartFromGroupOffsets();
        // 从最早的记录开始消费数据,忽略已提交的offset信息
        // stringFlinkKafkaConsumer.setStartFromEarliest();
        // 从最新的记录开始消费数据,忽略已提交的offset信息
        // stringFlinkKafkaConsumer.setStartFromLatest();
        // 从指定的时间戳开始消费数据,对于每个分区,其时间戳大于或者等于指定时间戳的记录将被作为起始位置
        // stringFlinkKafkaConsumer.setStartFromTimestamp(1111L);
        // 新的写法
        KafkaSource.builder()
                   .setProperties(properties)
                   .setTopics("")
                   .setStartingOffsets(OffsetsInitializer.earliest())
                   .build();
    }
}
2.2、Kafka Consumer的容错

Flink中也有这个checkpoint机制,checkpoint是flink实现容错的核心功能。它能够根据配置周期性的基于流中各个算子任务的State来生成快照,从而将这些State数据定期持久化存储下来。当flink程序一旦意外崩溃时,重新运行程序时可以有选择的从这些快照进行恢复。从而修正因为故障带来的程序数据异常。

  1. 当CheckPoint机制开启的时候,Consumer会定期把Kafka的offset信息还有其它算子任务的State信息一块保存起来
  2. 当Job失败重启时,Flink会从最近一次的CheckPoint中进行恢复数据,重新消费Kafka中的数据
  3. 为了能够使用支持容错的Consumer,需要开启CheckPoint
StreamExecutionEnvironment executionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
// 每隔5000ms执行一次checkpoint
executionEnvironment.enableCheckpointing(5000);
// 设置模式为EXACTLY_ONCE(默认值)
executionEnvironment.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 确保两次CheckPoint之间至少有多少ms间隔
executionEnvironment.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
// checkpoint必须在一分钟内完成。完不成就进行丢弃
executionEnvironment.getCheckpointConfig().setCheckpointTimeout(60000);
// 同一时间内只允许执行一个checkpoint
executionEnvironment.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 一旦Flink程序被Cancel后,会保留CheckPoint数据,以便根据实际需要恢复到指定的CheckPoint
executionEnvironment.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

执行CheckPoint时,State数据保存在哪?

  1. MemoryStateBackend Java堆内存中
  2. FsStateBackend 保存在TaskManager内存中,会把State的快照数据保存到配置的文件系统中
  3. RocksDBStateBackend 会在本地文件系统中维护这个State,State会直接写入本地的RocksDB中,同时需要配置一个远程的文件系统
2.3、Kafka Consumer Offset自动提交
  1. 针对Job是否开启CheckPoint来区分
  2. CheckPoint关闭时,通过参数enable.auto.commit和enable.commit.interval.ms控制
  3. CheckPoint开启时,执行CheckPoint的时候才会提交offset

三、KafkaProducer使用

  1. 启动kafka

    bin/kafka-server-start.sh -daemon config/server.properties

  2. 创建topic

    bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic my-topic --partitions 1

    image-20230127202408514

  3. 启动socket

    image-20230127214010738

  4. java代码

    package com.strivelearn.flink.kafka;
    ​
    import org.apache.flink.api.common.serialization.SimpleStringSchema;
    import org.apache.flink.streaming.api.datastream.DataStreamSource;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;
    import org.apache.flink.streaming.connectors.kafka.KafkaSerializationSchema;
    import org.apache.flink.streaming.connectors.kafka.internals.KafkaSerializationSchemaWrapper;
    import org.apache.flink.streaming.connectors.kafka.partitioner.FlinkFixedPartitioner;
    ​
    import java.util.Properties;
    ​
    /**
     * Flink向kafka中生产数据
     *
     * @author strivelearn
     * @version StreamKafkaSink.java, 2023年01月27日
     */
    public class StreamKafkaSink {
        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
            DataStreamSource<String> socketTextStream = environment.socketTextStream("192.168.234.100", 9001);
            // 指定需要写入的topic名称
            String topic = "test";
            Properties properties = new Properties();
            properties.setProperty("bootstrap.servers", "192.168.234.100:9092");
            // 自定义分区器实现将数据写入到指定topic具体的分区中。默认将所有的数据都写入到指定topic一个分区中
            FlinkFixedPartitioner<String> objectFlinkFixedPartitioner = new FlinkFixedPartitioner<>();
            KafkaSerializationSchema<String> objectKafkaSerializationSchema = new KafkaSerializationSchemaWrapper<>(topic, objectFlinkFixedPartitioner, true, new SimpleStringSchema());
            FlinkKafkaProducer<String> stringFlinkKafkaProducer = new FlinkKafkaProducer<>(topic, objectKafkaSerializationSchema, properties, FlinkKafkaProducer.Semantic.EXACTLY_ONCE);
            socketTextStream.addSink(stringFlinkKafkaProducer);
            environment.execute("StreamKafkaSink");
        }
    }
    
  5. 查看写入到kafka的消息

    bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning

    image-20230127220159572

3.1、Kafka Producer的容错
  1. 如果Flink开启了CheckPoint。针对FlinkKafkaProducer可以提供EXACTLY_ONCE的语义保证
  2. 可以通过semantic参数来指定三种不同的语义:Semantic.NONE、Semantic.AT_LAST_ONCE(默认)、Semantic.EXACTLY_ONCE
FlinkKafkaProducer<String> stringFlinkKafkaProducer = new FlinkKafkaProducer<>(topic, objectKafkaSerializationSchema, properties, FlinkKafkaProducer.Semantic.EXACTLY_ONCE);