Flink 1.11 SQL 快速上手,内含Demo及详细分析和使用过程,亲测可行!

Flink各个版本之间的API有比较大的gap,笔者将程序从Flink 1.7升级到Flink 1.11时,中间遇到了很多小问题。这里,给出一个使用Flink 1.11版本SQL API使用demo,并对需要注意的点和编写过程进行详细说明。

1. 需引入的pom依赖

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-clients_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

<!-- Table API and SQL components -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table</artifactId>
    <version>${flink.version}</version>
    <type>pom</type>
</dependency>

上面两个是Flink API和Table&SQL开发基础依赖,没什么好说的,新建项目时引入就对了。

<!-- blink planner -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-planner-blink_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

<!-- flink planner -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-planner_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

Flink 1.9 到 Flink 1.11 版本中,Flink old Planner和Blink Planner并存,且Flink 1.11将Blink Planner设置为默认Planner。因此开发的时候,需要根据需要引入其一,或两个都引入也可以。

注意:从maven上直接拷贝下来的pom依赖的设置为provided,本地执行flink程序时,会报找不到相应planner。删掉<scope>provided</scope>即可。

Exception in thread “main” org.apache.flink.table.api.NoMatchingTableFactoryException: Could not find a suitable table factory for ‘org.apache.flink.table.delegation.ExecutorFactory’ in
the classpath.

<!-- use the Table API & SQL for defining pipelines -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-api-java-bridge_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

使用Flink Table API和SQL API时需要适配不同语言的pipelines,因此需要根据自己实际开发语言引入bridge依赖,这里笔者使用java开发。当使用scala开发时,则引入相应scala-bridge。

<!-- kafka connector -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

<!-- flink-sql-connector-kafka -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-sql-connector-kafka_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>

实时开发时使用最多的数据源就是kafka,上面两个依赖分别为Flink API 和Flink SQL 使用kafka的connector。

注意:要注意flink与kafka版本适配,flink 1.11只支持kafka 10和11,否则程序运行时会报错。

<!-- flink-json -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-json</artifactId>
    <version>${flink.version}</version>
</dependency>

使用Flink的format格式解析文本时,需要引入相应format依赖。笔者程序中读取的数据为json格式,因此引入flink-json的依赖。

2. Flink SQL批处理Demo

话不多说,先给代码😋

import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;

/**
 * Created by search-lemon on 2020/10/27.
 * 读取本地url_parse_100.txt文件,解析后输出到控制台.
 */
public class BatchSqlTest {

    // use blink planner
    private static TableEnvironment tableEnv;

    public static void main(String[] args) {
        initBlinkEnv();
        registerFileSource();
        print();
    }

    private static void initBlinkEnv() {
        EnvironmentSettings tableEnvSettings = EnvironmentSettings
            .newInstance()
            .useBlinkPlanner() // 设置使用BlinkPlanner
            .inBatchMode() // 设置批处理模式
            .build();

        tableEnv = TableEnvironment.create(tableEnvSettings);
    }

    private static void registerFileSource() {

        String sourceSql = "CREATE TABLE url_parse_100 ("
            + " logtime STRING,"
            + " sign STRING,"
            + " version STRING"
            + " ) WITH ( "
            + " 'connector' = 'filesystem',"
            + " 'path' = 'E:/url_parse_100.txt',"
            + " 'format' = 'json'"
            + ")";

        tableEnv.executeSql(sourceSql); // 注册source表到env中
    }

    /**
     * 注册print table并输出数据.
     */
    private static void print() {

        String printTable = "CREATE TABLE print_table"
            + " WITH ('connector' = 'print')"
            + " LIKE url_parse_100 (EXCLUDING ALL)"; // 注册print表到env中

        tableEnv.executeSql(printTable);

        String printData = "INSERT INTO print_table"
            + " SELECT logtime, sign, version"
            + " FROM url_parse_100";

        tableEnv.executeSql(printData); // 输出数据到控制台

    }

上例很好理解,读取文本格式为json的 E:/url_parse_100.txt 文件,解析出 logtime, sign, version 三个字段,再将其输出到控制台。

3. Flink SQL流处理Demo

import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;

/**
 * Created by search-lemon on 2020/10/27.
 * 读取kafka数据,解析后输出到控制台.
 */
public class StreamSqlTest {

    // use blink planner
    private static TableEnvironment tableEnv;

    public static void main(String[] args) throws Exception {
        initBlinkEnv();
        registerKafkaSource();
        print();
    }

    private static void initBlinkEnv() {
        EnvironmentSettings tableEnvSettings = EnvironmentSettings
            .newInstance()
            .useBlinkPlanner()
            .inStreamingMode()
            .build();

        // stream也可使用StreamTableEnvironment
        tableEnv = TableEnvironment.create(tableEnvSettings);
    }

    /**
     * Flink 1.11只支持kafka 10和11版本.
     */
    private static void registerKafkaSource() {

        String sourceSql = "CREATE TABLE flink_input_test ("
            + " logtime STRING,"
            + " sign STRING,"
            + " version STRING"
            + " ) WITH ( "
            + " 'connector' = 'kafka',"
            + " 'topic' = 'flink_input_test'," // kafak topic
            + " 'properties.bootstrap.servers' = '*******'," // kafak brokers
            + " 'properties.group.id' = 'test_20201102'," // kafka group.id
            + " 'format' = 'json'," // kafka中数据格式
            + " 'scan.startup.mode' = 'latest-offset'" // 设置从最新offset开始消费
            + ")";

        tableEnv.executeSql(sourceSql);
    }

    /**
     * 注册print table并输出数据.
     */
    private static void print() {

        String printTable = "CREATE TABLE print_table"
            + " WITH ('connector' = 'print')"
            + " LIKE flink_input_test (EXCLUDING ALL)"; // 这里使用LIKE直接创建注册print表

        tableEnv.executeSql(printTable);

        String printData = "INSERT INTO print_table"
            + " SELECT logtime, sign, version"
            + " FROM flink_input_test";

        tableEnv.executeSql(printData);

    }

上述Batch和Stream的程序都使用了BlinkPlanner(),可以看出它们的程序非常相似,这是由于Flink 1.11的Blink Planner在Table&SQL API上已经实现了批流合一,都可以使用TableEnvironment对象作为程序运行环境,通过配置EnvironmentSettings控制运行的是实时还是离线程序。这样(除了source源表的注册)实时和离线系统完全可使用同一套代码,减少了大量重复的开发工作。

4. Old Planner与Blink Planner

4.1 Old Planner的使用

import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.bridge.java.BatchTableEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

/**
 * Created by search-lemon on 2020/10/27.
 * 测试初始化 Old Planner运行环境.
 */
public class InitEnvTest {

    // use flink old planner
    private static StreamTableEnvironment streamTableEnv;
    private static BatchTableEnvironment bTableEnv;

    public static void main(String[] args) {
        initFlinkBatchEnv(); // 初始化batch环境
        initFlinkStreamEnv(); // 初始化stream环境
    }

    private static void initFlinkBatchEnv() {
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        bTableEnv = BatchTableEnvironment.create(env);
    }

    private static void initFlinkStreamEnv() {
        EnvironmentSettings settings = EnvironmentSettings
            .newInstance()
            .useOldPlanner()
            .inStreamingMode()
            .build();

        StreamExecutionEnvironment streamEnv = StreamExecutionEnvironment.getExecutionEnvironment();
        streamTableEnv = StreamTableEnvironment.create(streamEnv, settings);
    }

可以看出,Flink Old Planner中批和流处理环境是分离的,需要分别使用StreamTableEnvironmentBatchTableEnvironment来运行实时和离线程序。

不同的Environment和Planner是配套使用的。当然,如果使用Blink Planner来处理流数据也可以使用StreamExecutionEnvironment。反之,Flink Old Planner没法儿使用批流合一的TableEnvironment。

4.2 Blink Planner的优势与局限

从Flink最近几个版本的发展来看,Blink Planner逐渐成为主流。Flink Old Planner是将Flink 1.9版本之前的核心执行计划优化和转化部分提取出来形成一个模块,与新开发的Blink Planner并列,提供两个Planner供用户自行选择。

Old Planner中,Table&SQL API是对底层API(DataStream和DataSet)的封装,批和流的处理完全是两套逻辑代码。而Blink Planner中很多SQL逻辑执行计划可直接转换为物理执行计划执行。架构上,Blink Planner Table&SQL API与DataStream API平行,不再是其简单的封装,舍弃了DataSet API,批和流处理使用同一套转换执行逻辑代码,实现了批流合一。但是局限也是很明显的,TableEnvironment不支持Table&SQL API和DataStream API之间的相互转换,是一个纯SQL的处理过程。这样从低版本直接升级上来的程序如果想要使用新特性就需要做较多适配工作。

Old PlannerBlink Planner(1.9以后版本)
Table API&SQL翻译为物理层API方式根据“流/批”输入决定翻译为DataStream还是DataSet程序无论输入为流或批,都翻译为DataStream程序(有些直接翻译为transfomation/底层执行,使得Table API&SQL与DataStream API在架构上是平行的)
Table API&SQL是否可以与物理层API集成(嵌套使用/互相转换)支持与DataSet API和DataStream API程序集成将批视为流的特殊情况,不支持Table和DataSet之间的转换,不支持BatchTableSource,使用bounded StreamTableSource替代(根据是否为bounded source区分批流)
Flink 1.11使用 Kafka Connector 时,可以通过设置 `timestamp.extractor` 参数来指定消息时间戳的提取方式。如果你想要获取 Kafka 消息的日志时间,可以使用 `LogAndSkipOnInvalidTimestamp` 提取方式,并将 `timestamp.extractor.watermark.delay-ms` 参数设置为 0。 具体来说,你需要在创建 Kafka 数据源时设置 `timestamp.extractor` 和 `timestamp.extractor.watermark.delay-ms` 参数,示例如下: ```java import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.util.serialization.SimpleStringSchema; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.typeinfo.Types; import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchema; import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchemaWrapper; import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchema.DeserializationSchemaWrapper; import java.util.Properties; import java.util.regex.Pattern; public class KafkaSourceExample { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); Properties properties = new Properties(); properties.setProperty("bootstrap.servers", "localhost:9092"); properties.setProperty("group.id", "test"); FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>( Pattern.compile("test-topic.*"), new LogAndSkipOnInvalidTimestamp<>(), // 设置 timestamp.extractor properties); consumer.setStartFromEarliest(); consumer.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks<String>() { @Override public long extractTimestamp(String element, long previousTimestamp) { // 不需要实现,因为我们已经在 Kafka Consumer 中设置了 timestamp.extractor return 0; } @Override public Watermark checkAndGetNextWatermark(String lastElement, long extractedTimestamp) { // 不需要实现,因为我们已经在 Kafka Consumer 中设置了 timestamp.extractor.watermark.delay-ms return null; } }); env .addSource(consumer) .print(); env.execute("Kafka Source Example"); } public static class LogAndSkipOnInvalidTimestamp<T> extends DeserializationSchemaWrapper<T> { public LogAndSkipOnInvalidTimestamp() { super(new SimpleStringSchema()); } @Override public T deserialize(byte[] messageKey, byte[] message, String topic, int partition, long offset) throws Exception { try { // 提取消息时间戳 Long timestamp = Long.valueOf(topic.split("-")[1]); // 构造一个带时间戳的元组 return (T) Tuple2.of(new String(messageKey), new String(message), timestamp); } catch (Exception e) { // 如果提取时间戳失败,则打印一条日志并跳过该条消息 System.err.println("Skip invalid message: " + new String(message)); return null; } } } } ``` 上述示例代码中,我们通过自定义 `LogAndSkipOnInvalidTimestamp` 类来实现了 `KafkaDeserializationSchema` 接口,并在其中提取了 Kafka 消息的日志时间戳。在 `deserialize` 方法中,我们将 Kafka 消息转换为一个带时间戳的元组,并在返回时进行了类型转换。 在 `main` 函数中,我们通过 `new LogAndSkipOnInvalidTimestamp<>()` 来设置了 `timestamp.extractor` 参数,并将 `timestamp.extractor.watermark.delay-ms` 参数设置为 0。这样就可以在 Flink SQL使用带时间戳的元组来进行数据处理了。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值