Spark Doris Connector
Spark Doris Connector 可以支持通过 Spark 读取 Doris 中存储的数据,也支持通过Spark写入数据到Doris。
代码库地址:<https://github.com/apache/doris-spark-connector>
- 支持从
Doris中读取数据 - 支持
Spark DataFrame批量/流式 写入Doris - 可以将
Doris表映射为DataFrame或者RDD,推荐使用DataFrame。 - 支持在
Doris端完成数据过滤,减少数据传输量。
版本兼容
| Connector | Spark | Doris | Java | Scala |
|---|---|---|---|---|
| 1.3.1 | 3.4 ~ 3.1, 2.4, 2.3 | 1.0 + | 8 | 2.12, 2.11 |
| 1.2.0 | 3.2, 3.1, 2.3 | 1.0 + | 8 | 2.12, 2.11 |
| 1.1.0 | 3.2, 3.1, 2.3 | 1.0 + | 8 | 2.12, 2.11 |
| 1.0.1 | 3.1, 2.3 | 0.12 - 0.15 | 8 | 2.12, 2.11 |
编译与安装
准备工作
- 修改
custom_env.sh.tpl文件,重命名为custom_env.sh - 在源码目录下执行:
sh build.sh根据提示输入你需要的 Scala 与 Spark 版本进行编译。
编译成功后,会在 dist 目录生成目标jar包,如:spark-doris-connector-3.2_2.12-1.2.0-SNAPSHOT.jar。 将此文件复制到 Spark 的 ClassPath 中即可使用 Spark-Doris-Connector。
例如,Local 模式运行的 Spark,将此文件放入 jars/ 文件夹下。Yarn集群模式运行的Spark,则将此文件放入预部署包中。
例如将 spark-doris-connector-3.2_2.12-1.2.0-SNAPSHOT.jar 上传到 hdfs 并在 spark.yarn.jars 参数上添加 hdfs 上的 Jar 包路径
- 上传
spark-doris-connector-3.2_2.12-1.2.0-SNAPSHOT.jar到hdfs。
1. hdfs dfs -mkdir /spark-jars/
2. hdfs dfs -put /your_local_path/spark-doris-connector-3.2_2.12-1.2.0-SNAPSHOT.jar /spark-jars/
- 在集群中添加
spark-doris-connector-3.2_2.12-1.2.0-SNAPSHOT.jar依赖。
1. spark.yarn.jars=hdfs:///spark-jars/spark-doris-connector-3.2_2.12-1.2.0-SNAPSHOT.jar
使用Maven管理
1. <dependency>
2. <groupId>org.apache.doris</groupId>
3. <artifactId>spark-doris-connector-3.4_2.12</artifactId>
4. <version>1.3.0</version>
5. </dependency>
注意
请根据不同的 Spark 和 Scala 版本替换相应的 Connector 版本。
使用示例
读取
SQL
1. CREATE
2. TEMPORARY VIEW spark_doris
3. USING doris
4. OPTIONS(
5. "table.identifier"="$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME",
6. "fenodes"="$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT",
7. "user"="$YOUR_DORIS_USERNAME",
8. "password"="$YOUR_DORIS_PASSWORD"
9. );
11. SELECT *
12. FROM spark_doris;
DataFrame
1. val dorisSparkDF = spark.read.format("doris")
2. .option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
3. .option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
4. .option("user", "$YOUR_DORIS_USERNAME")
5. .option("password", "$YOUR_DORIS_PASSWORD")
6. .load()
8. dorisSparkDF.show(5)
RDD
1. import org.apache.doris.spark._
3. val dorisSparkRDD = sc.dorisRDD(
4. tableIdentifier = Some("$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME"),
5. cfg = Some(Map(
6. "doris.fenodes" -> "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT",
7. "doris.request.auth.user" -> "$YOUR_DORIS_USERNAME",
8. "doris.request.auth.password" -> "$YOUR_DORIS_PASSWORD"
9. ))
10. )
12. dorisSparkRDD.collect()
pySpark
1. dorisSparkDF = spark.read.format("doris")
2. .option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
3. .option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
4. .option("user", "$YOUR_DORIS_USERNAME")
5. .option("password", "$YOUR_DORIS_PASSWORD")
6. .load()
7. // show 5 lines data
8. dorisSparkDF.show(5)
写入
SQL
1. CREATE
2. TEMPORARY VIEW spark_doris
3. USING doris
4. OPTIONS(
5. "table.identifier"="$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME",
6. "fenodes"="$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT",
7. "user"="$YOUR_DORIS_USERNAME",
8. "password"="$YOUR_DORIS_PASSWORD"
9. );
11. INSERT INTO spark_doris
12. VALUES ("VALUE1", "VALUE2", ...);
13. # or
14. INSERT INTO spark_doris
15. SELECT *
16. FROM YOUR_TABLE
17. # or
18. INSERT OVERWRITE
19. SELECT *
20. FROM YOUR_TABLE
DataFrame(batch/stream)
1. ## batch sink
2. val mockDataDF = List(
3. (3, "440403001005", "21.cn"),
4. (1, "4404030013005", "22.cn"),
5. (33, null, "23.cn")
6. ).toDF("id", "mi_code", "mi_name")
7. mockDataDF.show(5)
9. mockDataDF.write.format("doris")
10. .option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
11. .option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
12. .option("user", "$YOUR_DORIS_USERNAME")
13. .option("password", "$YOUR_DORIS_PASSWORD")
14. //其它选项
15. //指定你要写入的字段
16. .option("doris.write.fields", "$YOUR_FIELDS_TO_WRITE")
17. // 支持设置 Overwrite 模式来覆盖数据
18. // .option("save_mode", SaveMode.Overwrite)
19. .save()
21. ## stream sink(StructuredStreaming)
23. ### 结果 DataFrame 和 doris 表相同的结构化数据, 配置方式和批量模式一致。
24. val sourceDf = spark.readStream.
25. .format("your_own_stream_source")
26. .load()
28. val resultDf = sourceDf.<transformations>
30. resultDf.writeStream
31. .format("doris")
32. .option("checkpointLocation", "$YOUR_CHECKPOINT_LOCATION")
33. .option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
34. .option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
35. .option("user", "$YOUR_DORIS_USERNAME")
36. .option("password", "$YOUR_DORIS_PASSWORD")
37. .start()
38. .awaitTermination()
40. ### 结果 DataFrame 中存在某一列的数据可以直接写入的,比如符合导入规范的 Kafka 消息中的 value 值
42. val kafkaSource = spark.readStream
43. .format("kafka")
44. .option("kafka.bootstrap.servers", "$YOUR_KAFKA_SERVERS")
45. .option("startingOffsets", "latest")
46. .option("subscribe", "$YOUR_KAFKA_TOPICS")
47. .load()
48. kafkaSource.selectExpr("CAST(value as STRING)")
49. .writeStream
50. .format("doris")
51. .option("checkpointLocation", "$YOUR_CHECKPOINT_LOCATION")
52. .option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
53. .option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
54. .option("user", "$YOUR_DORIS_USERNAME")
55. .option("password", "$YOUR_DORIS_PASSWORD")
56. // 设置该选项可以将 Kafka 消息中的 value 列不经过处理直接写入
57. .option("doris.sink.streaming.passthrough", "true")
58. .option("doris.sink.properties.format", "json")
59. // 其他选项
60. .start()
61. .awaitTermination()
Java示例
samples/doris-demo/spark-demo/ 下提供了 Java 版本的示例,可供参考,这里
配置
通用配置项
| Key | Default Value | Comment |
|---|---|---|
| doris.fenodes | — | Doris FE http 地址,支持多个地址,使用逗号分隔 |
| doris.table.identifier | — | Doris 表名,如:db1.tbl1 |
| 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 | 一个RDD Partition对应的Doris Tablet个数。 此数值设置越小,则会生成越多的Partition。从而提升Spark侧的并行度,但同时会对Doris造成更大的压力。 |
| doris.read.field | — | 读取Doris表的列名列表,多列之间使用逗号分隔 |
| doris.batch.size | 1024 | 一次从BE读取数据的最大行数。增大此数值可减少Spark与Doris之间建立连接的次数。 从而减轻网络延迟所带来的额外时间开销。 |
| doris.exec.mem.limit | 2147483648 | 单个查询的内存限制。默认为 2GB,单位为字节 |
| doris.deserialize.arrow.async | false | 是否支持异步转换Arrow格式到spark-doris-connector迭代所需的RowBatch |
| doris.deserialize.queue.size | 64 | 异步转换Arrow格式的内部处理队列,当doris.deserialize.arrow.async为true时生效 |
| doris.write.fields | — | 指定写入Doris表的字段或者字段顺序,多列之间使用逗号分隔。 默认写入时要按照Doris表字段顺序写入全部字段。 |
| doris.sink.batch.size | 100000 | 单次写BE的最大行数 |
| doris.sink.max-retries | 0 | 写BE失败之后的重试次数 |
| doris.sink.properties.format | csv | Stream Load 的数据格式。 共支持3种格式:csv,json,arrow(1.4.0版本开始支持) 更多参数详情 |
| doris.sink.properties.* | — | Stream Load 的导入参数。 例如: 指定列分隔符:‘doris.sink.properties.column_separator’ = ‘,’等 更多参数详情 |
| doris.sink.task.partition.size | — | Doris写入任务对应的 Partition 个数。Spark RDD 经过过滤等操作,最后写入的 Partition 数可能会比较大,但每个 Partition 对应的记录数比较少,导致写入频率增加和计算资源浪费。 此数值设置越小,可以降低 Doris 写入频率,减少 Doris 合并压力。该参数配合 doris.sink.task.use.repartition 使用。 |
| doris.sink.task.use.repartition | false | 是否采用 repartition 方式控制 Doris写入 Partition数。默认值为 false,采用 coalesce 方式控制(注意: 如果在写入之前没有 Spark action 算子,可能会导致整个计算并行度降低)。 如果设置为 true,则采用 repartition 方式(注意: 可设置最后 Partition 数,但会额外增加 shuffle 开销)。 |
| doris.sink.batch.interval.ms | 50 | 每个批次sink的间隔时间,单位 ms。 |
| doris.sink.enable-2pc | false | 是否开启两阶段提交。开启后将会在作业结束时提交事务,而部分任务失败时会将所有预提交状态的事务会滚。 |
| doris.sink.auto-redirect | true | 是否重定向 StreamLoad 请求。开启后 StreamLoad 将通过 FE 写入, 不再显式获取 BE 信息。 |
SQL 和 Dataframe 专有配置
| Key | Default Value | Comment |
|---|---|---|
| user | — | 访问Doris的用户名 |
| password | — | 访问Doris的密码 |
| doris.filter.query.in.max.count | 100 | 谓词下推中,in表达式value列表元素最大数量。超过此数量,则in表达式条件过滤在Spark侧处理。 |
| doris.ignore-type | — | 指在定临时视图中,读取 schema 时要忽略的字段类型。 例如,’doris.ignore-type’=’bitmap,hll’ |
Structured Streaming 专有配置
| Key | Default Value | Comment |
|---|---|---|
| doris.sink.streaming.passthrough | false | 将第一列的值不经过处理直接写入。 |
RDD 专有配置
| Key | Default Value | Comment |
|---|---|---|
| doris.request.auth.user | — | 访问Doris的用户名 |
| doris.request.auth.password | — | 访问Doris的密码 |
| doris.filter.query | — | 过滤读取数据的表达式,此表达式透传给Doris。Doris使用此表达式完成源端数据过滤。 |
提示
- 在 Spark SQL 中,通过 insert into 方式写入数据时,如果 doris 的目标表中包含
BITMAP或HLL类型的数据时,需要设置参数doris.ignore-type为对应类型, 并通过doris.write.fields对列进行映射转换,使用方式如下:
BITMAP
1. CREATE TEMPORARY VIEW spark_doris 2. USING doris 3. OPTIONS( 4. "table.identifier"="$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME", 5. "fenodes"="$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT", 6. "user"="$YOUR_DORIS_USERNAME", 7. "password"="$YOUR_DORIS_PASSWORD" 8. "doris.ignore-type"="bitmap", 9. "doris.write.fields"="col1,col2,col3,bitmap_col2=to_bitmap(col2),bitmap_col3=bitmap_hash(col3)" 10. );HLL
1. CREATE TEMPORARY VIEW spark_doris 2. USING doris 3. OPTIONS( 4. "table.identifier"="$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME", 5. "fenodes"="$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT", 6. "user"="$YOUR_DORIS_USERNAME", 7. "password"="$YOUR_DORIS_PASSWORD" 8. "doris.ignore-type"="hll", 9. "doris.write.fields"="col1,hll_col1=hll_hash(col1)" 10. );2. 从 1.3.0 版本开始,doris.sink.max-retries配置项的默认值为 0,即默认不进行重试。 当设置该参数大于 0 时,会进行批次级别的失败重试,会在 Spark Executor 内存中缓存doris.sink.batch.size所配置大小的数据,可能需要适当增大内存分配。 3. 从 1.3.0 版本开始,支持 overwrite 模式写入(只支持全表级别的数据覆盖),具体使用方式如下DataFrame
1. resultDf.format("doris") 2. .option("doris.fenodes","$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT") 3. // your own options 4. .option("save_mode", SaveMode.Overwrite) 5. .save()SQL
1. INSERT OVERWRITE your_target_table 2. SELECT * FROM your_source_table
Doris 和 Spark 列类型映射关系
| Doris Type | Spark Type |
|---|---|
| NULL_TYPE | DataTypes.NullType |
| BOOLEAN | DataTypes.BooleanType |
| TINYINT | DataTypes.ByteType |
| SMALLINT | DataTypes.ShortType |
| INT | DataTypes.IntegerType |
| BIGINT | DataTypes.LongType |
| FLOAT | DataTypes.FloatType |
| DOUBLE | DataTypes.DoubleType |
| DATE | DataTypes.DateType |
| DATETIME | DataTypes.StringType1 |
| DECIMAL | DecimalType |
| CHAR | DataTypes.StringType |
| LARGEINT | DecimalType |
| VARCHAR | DataTypes.StringType |
| TIME | DataTypes.DoubleType |
| HLL | Unsupported datatype |
| Bitmap | Unsupported datatype |
- 注:Connector 中,将
DATETIME映射为String。由于Doris底层存储引擎处理逻辑,直接使用时间类型时,覆盖的时间范围无法满足需求。所以使用String类型直接返回对应的时间可读文本。
