因为公司要处理流量数据,其中设计到了会话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知识也可以去官方文档学习。

pyspark读写hive pyspark读写doris_pyspark读写hive