因为公司要处理流量数据,其中设计到了会话id的处理,从而需要用spark来实现这一功能。
而公司的数仓是基于Doris搭建的,这就涉及到了spark读写Doris,简单来说一下spark读写Doris具体的实现方案
01jdbc读写
因为Doris支持mysql协议,所以可以直接通过spark使用jdbc的方式来读写Doris.
Pom相关依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
仅仅需要导入连接mysql的依赖就可以了。
spark 读取 doris表中数据实现:
//创建sparkconf
val sparkConf: SparkConf = new SparkConf().setAppName("logSession").setMaster("local[*]")
//创建sparksession
val sparkSession:SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()
//根据参数跑指定日期范围的数据
val querySql =
s"""(select
|login_id,event,properties,login_id,distinct_id,event_at,dt,time from dev_ods.ods_user_log
|where event_at >'${startTime}' and dt between '${startTime}'and '${endTime}'
| ) tmp"""
println(querySql)
//读取doris
val dataDf = sparkSession
.read
.format("jdbc")
.option("url", "jdbc:mysql://10.150.60.2:9030")
.option("fetchsize", "500000")
.option("driver", "com.mysql.jdbc.Driver")
.option("user", "root")
.option("password", "bigdata1234")
.option("dbtable",querySql )
.load()
spark 写入 doris表中数据实现:
userLogDF.show()
userLogDF
.write
.format("jdbc")
.mode(SaveMode.Append)
.option("driver", "com.mysql.jdbc.Driver")
.option("url", "jdbc:mysql://10.150.60.2:9030")
.option("batchsize", "50000")
.option("user", "root")
.option("password", "bigdata1234")
.option("isolationLevel", "NONE")
.option("dbtable","dev_dwd.dwd_user_act" )
.save()
Jdbc这种方式可以实现读写doris但是写入速度会出现效率很低的情况,不适于大数据量的写入,所以介绍第二种方式,doris官方提供的连接依赖。
02 Doris官方依赖
Pom依赖:
<dependency>
<groupId>org.apache.doris</groupId>
<artifactId>spark-doris-connector-3.1_2.12</artifactId>
<!--artifactId>spark-doris-connector-2.3_2.11</artifactId-->
<version>1.0.1</version>
</dependency>
该pom依赖在前几日还是没有的,这个jar包需要自己编译,其中的坑和曲折真的是太...... ,还好现在官方直接更新了文档了,提供了对应的pom依赖,感谢官方!
spark 读取 doris表中数据实现:
val dorisSparkDF = spark.read.format("doris")
.option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
.option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
.option("user", "$YOUR_DORIS_USERNAME")
.option("password", "$YOUR_DORIS_PASSWORD")
.load()
dorisSparkDF.show(5)
spark 读取 doris表中数据实现:
val mockDataDF = List(
(3, "440403001005", "21.cn"),
(1, "4404030013005", "22.cn"),
(33, null, "23.cn")
).toDF("id", "mi_code", "mi_name")
mockDataDF.show(5)
mockDataDF.write.format("doris")
.option("doris.table.identifier", "$YOUR_DORIS_DATABASE_NAME.$YOUR_DORIS_TABLE_NAME")
.option("doris.fenodes", "$YOUR_DORIS_FE_HOSTNAME:$YOUR_DORIS_FE_RESFUL_PORT")
.option("user", "$YOUR_DORIS_USERNAME")
.option("password", "$YOUR_DORIS_PASSWORD")
//其它选项
//指定你要写入的字段
.option("doris.write.fields","$YOUR_FIELDS_TO_WRITE")
.save()
官方读写doris样例:
object DorisSparkConnectionExampleScala {
val DORIS_DB = "demo"
val DORIS_TABLE = "example_table"
val DORIS_FE_IP = "your doris fe ip"
val DORIS_FE_HTTP_PORT = "8030"
val DORIS_FE_QUERY_PORT = "9030"
val DORIS_USER = "your doris user"
val DORIS_PASSWORD = "your doris password"
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("test").setMaster("local[*]")
// if you want to run readWithRdd(sparkConf), please comment this line
// val sc = SparkSession.builder().config(sparkConf).getOrCreate()
readWithRdd(sparkConf)
// readWithDataFrame(sc)
// readWithSql(sc)
// readWithJdbc(sc)
}
/**
* read doris table Using Spark Rdd
*/
def readWithRdd(sparkConf: SparkConf): Unit = {
val scf = new SparkContextFunctions(new SparkContext(sparkConf))
val rdd = scf.dorisRDD(
tableIdentifier = Some(s"$DORIS_DB.$DORIS_TABLE"),
cfg = Some(Map(
"doris.fenodes" -> s"$DORIS_FE_IP:$DORIS_FE_HTTP_PORT",
"doris.request.auth.user" -> DORIS_USER,
"doris.request.auth.password" -> DORIS_PASSWORD
))
)
val resultArr = rdd.collect()
println(resultArr.mkString)
}
/**
* read doris table Using DataFrame
*
* @param sc SparkSession
*/
def readWithDataFrame(sc: SparkSession): Unit = {
val df = sc.read.format("doris")
.option("doris.table.identifier", s"$DORIS_DB.$DORIS_TABLE")
.option("doris.fenodes", s"$DORIS_FE_IP:$DORIS_FE_HTTP_PORT")
.option("user", DORIS_USER)
.option("password", DORIS_PASSWORD)
.load()
df.show(5)
}
/**
* read doris table Using Spark Sql
*
* @param sc SparkSession
*/
def readWithSql(sc: SparkSession): Unit = {
sc.sql("CREATE TEMPORARY VIEW spark_doris\n" +
"USING doris " +
"OPTIONS( " +
" \"table.identifier\"=\"" + DORIS_DB + "." + DORIS_TABLE + "\", " +
" \"fenodes\"=\"" + DORIS_FE_IP + ":" + DORIS_FE_HTTP_PORT + "\", " +
" \"user\"=\"" + DORIS_USER + "\", " +
" \"password\"=\"" + DORIS_PASSWORD + "\" " +
")")
sc.sql("select * from spark_doris").show(5)
}
/**
* read doris table Using jdbc
*
* @param sc SparkSession
*/
def readWithJdbc(sc: SparkSession): Unit = {
val df = sc.read.format("jdbc")
.option("url", s"jdbc:mysql://$DORIS_FE_IP:$DORIS_FE_QUERY_PORT/$DORIS_DB?useUnicode=true&characterEncoding=utf-8")
.option("dbtable", DORIS_TABLE)
.option("user", DORIS_USER)
.option("password", DORIS_PASSWORD)
.load()
df.show(5)
}
}
参数解读
通用配置项
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.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表字段顺序写入全部字段。 |
sink.batch.size | 10000 | 单次写BE的最大行数 |
sink.max-retries | 1 | 写BE失败之后的重试次数 |
SQL 和 Dataframe 专有配置
Key | Default Value | Comment |
user | -- | 访问Doris的用户名 |
password | -- | 访问Doris的密码 |
doris.filter.query.in.max.count | 100 | 谓词下推中,in表达式value列表元素最大数量。超过此数量,则in表达式条件过滤在Spark侧处理。 |
特地做了新老官方文档的对比,可以看到新文档对于doris使用的介绍更加的全面和丰富,相信这个由我们国内百度开源的OLAP数据分析型数据库一定可以越来越好,被更多的国内外公司认可,和国外的clickhouse齐头并进。
如果想要了解更多的spark读写doris使用细节可以去官方文档。我图中画出来的路径看更多的使用方法,当然如果想要学习更多的doris知识也可以去官方文档学习。