Flink Doris Connector

Flink Doris Connector 可以支持通过 Flink 操作(读取、插入、修改、删除) Doris 中存储的数据。

代码库地址:<https://github.com/apache/incubator-doris-flink-connector>

  • 可以将Doris表映射为DataStream或者Table。

注意:

  1. 修改和删除只支持在Unique Key模型上
  2. 目前的删除是支持Flink CDC的方式接入数据实现自动删除,如果是其他数据接入的方式删除需要自己实现。Flink CDC的数据删除使用方式参照本文档最后一节

版本兼容

Connector Flink Doris Java Scala
1.11.6-2.12-xx 1.11.x 0.13+ 8 2.12
1.12.7-2.12-xx 1.12.x 0.13.+ 8 2.12
1.13.5-2.12-xx 1.13.x 0.13.+ 8 2.12

编译与安装

在源码目录下执行:

1. sh build.sh --flink 1.11.6 --scala 2.12 # flink 1.11.6 scala 2.12

注:如果你是从 tag 检出的源码,则可以直接执行 sh build.sh --tag,而无需指定 flink 和 scala 的版本。因为 tag 源码中的版本是固定的。比如 1.13.5-2.12-1.0.1 表示 flink 版本 1.13.5,scala 版本 2.12,connector 版本 1.0.1。

编译成功后,会在 output/ 目录下生成文件,如:doris-flink-1.13.5-2.12-1.0.1-SNAPSHOT.jar 。将此文件复制到 Flink 的 ClassPath 中即可使用 Flink-Doris-Connector 。例如, Local 模式运行的 Flink ,将此文件放入 jars/ 文件夹下。 Yarn 集群模式运行的 Flink ,则将此文件放入预部署包中。

备注

  1. doris FE 要在配置中配置启用http v2
  2. Scala版本目前只支持2.12.x版本

conf/fe.conf

1. enable_http_server_v2 = true

使用Maven 管理

添加 maven 依赖

1. <dependency>
2. <groupId>org.apache.doris</groupId>
3. <artifactId>doris-flink-connector</artifactId>
4. <version>1.11.6-2.12-SNAPSHOT</version>
5. </dependency>

备注

1.11.6 可以根据flink 版本替换成替换成 1.12.7 或者 1.13.5

使用方法

Flink读写Doris数据主要有三种方式

  • SQL
  • DataStream
  • DataSet

参数配置

Flink Doris Connector Sink的内部实现是通过 Stream load 服务向Doris写入数据, 同时也支持 Stream load 请求参数的配置设定

参数配置方法

  • SQL 使用WITH参数sink.properties.配置
  • DataStream 使用方法DorisExecutionOptions.builder().setStreamLoadProp(Properties)配置

SQL

  • Source
1. CREATE TABLE flink_doris_source (
2. name STRING,
3. age INT,
4. price DECIMAL(5,2),
5. sale DOUBLE
6. )
7. WITH (
8. 'connector' = 'doris',
9. 'fenodes' = '$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT',
10. 'table.identifier' = '$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME',
11. 'username' = '$YOUR_DORIS_USERNAME',
12. 'password' = '$YOUR_DORIS_PASSWORD'
13. );
  • Sink
1. CREATE TABLE flink_doris_sink (
2. name STRING,
3. age INT,
4. price DECIMAL(5,2),
5. sale DOUBLE
6. )
7. WITH (
8. 'connector' = 'doris',
9. 'fenodes' = '$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT',
10. 'table.identifier' = '$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME',
11. 'username' = '$YOUR_DORIS_USERNAME',
12. 'password' = '$YOUR_DORIS_PASSWORD'
13. );
  • Insert
1. INSERT INTO flink_doris_sink select name,age,price,sale from flink_doris_source

DataStream

  • Source
1. Properties properties = new Properties();
2. properties.put("fenodes","FE_IP:8030");
3. properties.put("username","root");
4. properties.put("password","");
5. properties.put("table.identifier","db.table");
6. env.addSource(new DorisSourceFunction(
7. new DorisStreamOptions(properties),
8. new SimpleListDeserializationSchema()
9. )
10. ).print();
  • Sink

Json数据流

1. Properties pro = new Properties();
2. pro.setProperty("format", "json");
3. pro.setProperty("strip_outer_array", "true");
4. env.fromElements(
5. "{\"longitude\": \"116.405419\", \"city\": \"北京\", \"latitude\": \"39.916927\"}"
6. )
7. .addSink(
8. DorisSink.sink(
9. DorisReadOptions.builder().build(),
10. DorisExecutionOptions.builder()
11. .setBatchSize(3)
12. .setBatchIntervalMs(0l)
13. .setMaxRetries(3)
14. .setStreamLoadProp(pro).build(),
15. DorisOptions.builder()
16. .setFenodes("FE_IP:8030")
17. .setTableIdentifier("db.table")
18. .setUsername("root")
19. .setPassword("").build()
20. ));

Json数据流

1. env.fromElements(
2. "{\"longitude\": \"116.405419\", \"city\": \"北京\", \"latitude\": \"39.916927\"}"
3. )
4. .addSink(
5. DorisSink.sink(
6. DorisOptions.builder()
7. .setFenodes("FE_IP:8030")
8. .setTableIdentifier("db.table")
9. .setUsername("root")
10. .setPassword("").build()
11. ));

RowData数据流

1. DataStream<RowData> source = env.fromElements("")
2. .map(new MapFunction<String, RowData>() {
3. @Override
4. public RowData map(String value) throws Exception {
5. GenericRowData genericRowData = new GenericRowData(3);
6. genericRowData.setField(0, StringData.fromString("北京"));
7. genericRowData.setField(1, 116.405419);
8. genericRowData.setField(2, 39.916927);
9. return genericRowData;
10. }
11. });

13. String[] fields = {"city", "longitude", "latitude"};
14. LogicalType[] types = {new VarCharType(), new DoubleType(), new DoubleType()};

16. source.addSink(
17. DorisSink.sink(
18. fields,
19. types,
20. DorisReadOptions.builder().build(),
21. DorisExecutionOptions.builder()
22. .setBatchSize(3)
23. .setBatchIntervalMs(0L)
24. .setMaxRetries(3)
25. .build(),
26. DorisOptions.builder()
27. .setFenodes("FE_IP:8030")
28. .setTableIdentifier("db.table")
29. .setUsername("root")
30. .setPassword("").build()
31. ));

DataSet

  • Sink
1. MapOperator<String, RowData> data = env.fromElements("")
2. .map(new MapFunction<String, RowData>() {
3. @Override
4. public RowData map(String value) throws Exception {
5. GenericRowData genericRowData = new GenericRowData(3);
6. genericRowData.setField(0, StringData.fromString("北京"));
7. genericRowData.setField(1, 116.405419);
8. genericRowData.setField(2, 39.916927);
9. return genericRowData;
10. }
11. });

13. DorisOptions dorisOptions = DorisOptions.builder()
14. .setFenodes("FE_IP:8030")
15. .setTableIdentifier("db.table")
16. .setUsername("root")
17. .setPassword("").build();
18. DorisReadOptions readOptions = DorisReadOptions.defaults();
19. DorisExecutionOptions executionOptions = DorisExecutionOptions.defaults();

21. LogicalType[] types = {new VarCharType(), new DoubleType(), new DoubleType()};
22. String[] fields = {"city", "longitude", "latitude"};

24. DorisDynamicOutputFormat outputFormat = new DorisDynamicOutputFormat(
25. dorisOptions, readOptions, executionOptions, types, fields
26. );

28. outputFormat.open(0, 1);
29. data.output(outputFormat);
30. outputFormat.close();

配置

通用配置项

Key Default Value Comment
fenodes — Doris FE http 地址
table.identifier — Doris 表名,如:db1.tbl1
username — 访问Doris的用户名
password — 访问Doris的密码
doris.request.retries 3 向Doris发送请求的重试次数
doris.request.connect.timeout.ms 30000 向Doris发送请求的连接超时时间
doris.request.read.timeout.ms 30000 向Doris发送请求的读取超时时间
doris.request.query.timeout.s 3600 查询doris的超时时间,默认值为1小时,-1表示无超时限制
doris.request.tablet.size Integer. MAX_VALUE 一个Partition对应的Doris Tablet个数。 此数值设置越小,则会生成越多的Partition。从而提升Flink侧的并行度,但同时会对Doris造成更大的压力。
doris.batch.size 1024 一次从BE读取数据的最大行数。增大此数值可减少flink与Doris之间建立连接的次数。 从而减轻网络延迟所带来的的额外时间开销。
doris.exec.mem.limit 2147483648 单个查询的内存限制。默认为 2GB,单位为字节
doris.deserialize.arrow.async false 是否支持异步转换Arrow格式到flink-doris-connector迭代所需的RowBatch
doris.deserialize.queue.size 64 异步转换Arrow格式的内部处理队列,当doris.deserialize.arrow.async为true时生效
doris.read.field — 读取Doris表的列名列表,多列之间使用逗号分隔
doris.filter.query — 过滤读取数据的表达式,此表达式透传给Doris。Doris使用此表达式完成源端数据过滤。
sink.batch.size 10000 单次写BE的最大行数
sink.max-retries 1 写BE失败之后的重试次数
sink.batch.interval 10s flush 间隔时间,超过该时间后异步线程将 缓存中数据写入BE。 默认值为10秒,支持时间单位ms、s、min、h和d。设置为0表示关闭定期写入。
sink.properties.* — Stream load 的导入参数 例如: ‘sink.properties.column_separator’ = ‘, ‘ 定义列分隔符 ‘sink.properties.escape_delimiters’ = ‘true’ 特殊字符作为分隔符,’\x01’会被转换为二进制的0x01 ‘sink.properties.format’ = ‘json’ ‘sink.properties.strip_outer_array’ = ‘true’ JSON格式导入
sink.enable-delete true 是否启用删除。此选项需要Doris表开启批量删除功能(0.15+版本默认开启),只支持Uniq模型。
Doris Type Flink Type
NULL_TYPE NULL
BOOLEAN BOOLEAN
TINYINT TINYINT
SMALLINT SMALLINT
INT INT
BIGINT BIGINT
FLOAT FLOAT
DOUBLE DOUBLE
DATE STRING
DATETIME STRING
DECIMAL DECIMAL
CHAR STRING
LARGEINT STRING
VARCHAR STRING
DECIMALV2 DECIMAL
TIME DOUBLE
HLL Unsupported datatype
1. CREATE TABLE cdc_mysql_source (
2. id int
3. ,name VARCHAR
4. ,PRIMARY KEY (id) NOT ENFORCED
5. ) WITH (
6. 'connector' = 'mysql-cdc',
7. 'hostname' = '127.0.0.1',
8. 'port' = '3306',
9. 'username' = 'root',
10. 'password' = 'password',
11. 'database-name' = 'database',
12. 'table-name' = 'table'
13. );

15. -- 支持删除事件同步(sink.enable-delete='true'),需要Doris表开启批量删除功能
16. CREATE TABLE doris_sink (
17. id INT,
18. name STRING
19. )
20. WITH (
21. 'connector' = 'doris',
22. 'fenodes' = '127.0.0.1:8030',
23. 'table.identifier' = 'database.table',
24. 'username' = 'root',
25. 'password' = '',
26. 'sink.properties.format' = 'json',
27. 'sink.properties.strip_outer_array' = 'true',
28. 'sink.enable-delete' = 'true'
29. );

31. insert into doris_sink select id,name from cdc_mysql_source;