在这里,我很荣幸的宣布,开源项目 flink sqlSubmit 1.0 发布了。

乘着这几天假,在家把很早就该完成的事情做了点,第一个就是把sqlSubmit 稍微修改了下,勉强发布1.0版本。

地址:https://github.com/springMoon/sqlSubmit/releases/tag/sqlSubmit-1.0

功能

  sqlSubmit 目前是基于 flink 1.10.1 开发,目前只测试过如下功能(就是简单的跑了下)

  • kafka Source/Sink

  • Kafak Upsert Sink

项目中resources/sql/connector 中 kafkaToKafka.sql 和 kafkaUpserDemo.sql

由于环境比较恶劣(只有一台老mac电脑),只装了kafka,所以这次就只有 kafka 相关的功能。

HiveCatalog 也因为没有 hive 暂时放弃。

等我在电脑上捣腾上mysql/hbase/elasticsearch 就加上对应的sql demo 文件。

KafkaUpsertSink

kafka source/sink 没什么好说的,就简单介绍下 KafkaUpsertSink,目前的 flink 版本中,kafka Sink 类:KafkaTableSink 继承自 AppendStreamTableSink,所以 kafka 表作为 sink upsert-mode 只支持 append,但是 sql 中使用了 group by 后,输出的流就变成 UpsertStream ,然后不能执行。

对一般kafka sink 表,执行如下 sql

-- kafka sink
CREATE TABLE user_log_sink (
  user_id VARCHAR
  ,max_tx BIGINT
) WITH (
  'connector.type' = 'kafka'
  ,'connector.version' = 'universal'
  ,'connector.topic' = 'user_behavior_sink'                            -- required: topic name from which the table is read
  ,'connector.properties.zookeeper.connect' = 'localhost:2181'    -- required: specify the ZooKeeper connection string
  ,'connector.properties.bootstrap.servers' = 'localhost:9092'    -- required: specify the Kafka server connection string
  ,'connector.properties.group.id' = 'user_log'                   -- optional: required in Kafka consumer, specify consumer group
  ,'connector.startup-mode' = 'group-offsets'                     -- optional: valid modes are "earliest-offset", "latest-offset", "group-offsets",  "specific-offsets"
  ,'connector.sink-partitioner' = 'fixed'                         --optional fixed 每个 flink 分区数据只发到 一个 kafka 分区
                                                                          -- round-robin flink 分区轮询分配到 kafka 分区
                                                                          -- custom 自定义分区策略
  --,'connector.sink-partitioner-class' = 'org.mycompany.MyPartitioner'   -- 自定义分区类
  ,'format.type' = 'json'                 -- required:  'csv', 'json' and 'avro'.
);

-- insert
insert into user_log_sink
select user_id, count(user_id)
from user_log
group by user_id;

会报如下错误:

org.apache.flink.table.api.TableException: AppendStreamTableSink requires that Table has only insert changes.

这种时候,很容易就想到自定义一个KafkaUsertSink,实现 UpsertStreamTableSink。(站在巨人的肩膀上)只需要参照Flink 中的KafkaTableSink 一套即可。


KafkaTableSink
KafkaTableSinkBase
KafkaTableSourceSinkFactory
KafkaTableSourceSinkFactoryBase
KafkaValidator

将 KafkaTableSinkBase 实现的接口改为 UpsertStreamTableSink ,然后实现对应的方法即可。 我把KafkaTableSinkBase 和 KafkaTableSink 合并了一下,代码目录 com.rookie.submit.connector.kafka

KafkaUpsertTableSink
KafkaUpsertTableSinkFactory
KafkaUpsertTableSinkFactoryBase
UpsertKafkaValidator

KafkaUpserTableSink 实现 UpsertStreamTableSink

UpsertKafkaValidator 则是修改对应的 table properties,将 'connector.type' = 'kafka' , 改为 'connector.type' = 'upsertKafka'

最重要的一段逻辑是将 DataStream<Tuple2<Boolean, Row>> 转为 DataStream<Row> ,将 UpsertStream 的 标示列去掉(根据自己实际的业务需要修改)

@Override
    public DataStreamSink<?> consumeDataStream(DataStream<Tuple2<Boolean, Row>> dataStream) {

        final SinkFunction<Row> kafkaProducer = createKafkaProducer(
                topic,
                properties,
                serializationSchema,
                partitioner);

        // todo cast DataStream<Tuple2<Boolean, Row>> to DataStream<Row>
        return dataStream
                .flatMap(new FlatMapFunction<Tuple2<Boolean, Row>, Row>() {
                    @Override
                    public void flatMap(Tuple2<Boolean, Row> element, Collector<Row> out) throws Exception {
                        // upsertStream include insert/update/delete change, true is upsert, false is delete
                        if (element.f0) {
                            out.collect(element.f1);
                        } else {
                            System.out.println("KafkaUpsertTableSinkBase : retract stream f0 will be false");
                        }
                    }
                })
                .addSink(kafkaProducer)
                .setParallelism(dataStream.getParallelism())
                .name(TableConnectorUtils.generateRuntimeName(this.getClass(), getFieldNames()));
    }

测试时,执行 com.rookie.submit.main.SqlSubmit ,需要传入参数: --sql sql/connector/kafkaUpsertDemo.sql (--sql 参数指定要执行的sql 脚本路径)

执行结果如下:

具体请移步 github 查看项目代码。

注:本人水平有限,开发的代码也比较鸡肋,建议使用 flink on zeppelin,flink 社区大力支持中。

flink 1.10.1 web UI 也优化了很多,比之前好看多了