Bootstrap

spark外部数据源(hive和jdbc)

Hive数据源

Spark on hive 与 Hive on Spark 的区别?我们说不管是Spark on Hive 还是 Hive on Spark,这两种不同的方式将 Spark 和 Hive 集成在一起。主要区别如下:

Spark on Hive:这是指在 Spark 中使用 Hive 的功能。Spark 提供了一个称为 HiveContext(在 Spark 2.0+ 版本中被 SparkSession 取代)的 API,通过该 API可以使用 Hive 的元数据和查询语言(HiveQL)来访问和操作 Hive 表。Spark on Hive 能够在 Spark 中使用 Hive 的表和数据,以及执行 HiveQL 查询。它允许你使用 Spark 的强大分布式计算功能,并与 Hive 集成。

Hive on Spark:这是指在 Hive 中使用 Spark 的执行引擎。在传统的 Hive 中,查询通常使用 MapReduce 作为执行引擎,但这可能导致较高的延迟。为了改善查询性能,Hive 也提供了一个称为 SparkExecutionEngine 的选项,该选项将查询的执行转移到 Spark 引擎上。可以通过使用set hive.execution.engine=spark的方式切换hive底层的执行引擎。使用 Hive on Spark,你可以利用 Spark 的内存计算和并行处理能力来加速 Hive 查询。这种方式允许你在 Hive 中继续使用 HiveQL,但底层的执行引擎变为 Spark。

总结起来,Spark on Hive 是在 Spark 中使用 Hive 功能,而 Hive on Spark 是在 Hive 中使用 Spark 的执行引擎。这两种集成方式都有助于将 Spark 和 Hive 的功能结合起来,从而提供更好的数据处理和查询性能。至于说具体选择哪种方式取决于你所在项目中的具体的需求和技术选项,两种方式都有很多企业在使用,但是一般我们推荐的方式都是Spark on Hive,即在Spark中使用Hive功能。

Hive数据源读取和写入

首先在IDEA中需要添加如下的依赖:

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-hive_${scala.version}</artifactId>
    <version>${spark.version}</version>
</dependency>

然后操作demo如下(如果下面的关于hive的注释不打开的话,需要将core-site.xml、hdfs-site.xml、hive-site.xml这三个配置文件放在项目的resources目录下)

 def main(args: Array[String]): Unit = {

    // 指定写入HDFS的用户
    System.setProperty("HADOOP_USER_NAME", "hdfs")

    val spark: SparkSession = SparkSession
      .builder()
      .appName("Java Spark Hive Example")
      .master("local[*]")
//      .config("hive.metastore.uris", "thrift://hadoop001:9083")
      // 如果配置了ha,这里就要写ha,然后需要将core-site.xml和hdfs-site.xml放在resources目录下,
      // 否则直接写hdfs://ip:port/user/hive/warehouse
      // .config("spark.sql.warehouse.dir", "hdfs://hadoop004:8020/user/hive/warehouse")
//      .config("spark.sql.warehouse.dir", "hdfs://nameservice1/user/hive/warehouse")
      .enableHiveSupport()
      .getOrCreate()

    spark.catalog.listDatabases().show(false)
//    spark.sql("create database ds_demo")

    // 从hive中读取数据
//    spark.read.table("default.tt").show()
//    spark.sql("select * from wikidata.ods_bsc_wikidata limit 10").show(false)
//    // 底层调用的就是read.table(tableName)
//    spark.table("ds_spark.people").show()

    //将hive写入数据表中
    // 不用创建,使用saveAsTable发现表不存在会自动创建
    spark.read.option("header", "true").csv("file:///D:/programming/second_spark/src/main/resources/people.csv").write.saveAsTable("default.people2")
    println(spark.read.table("default.people2").count)
//    spark.read.option("header", "true").csv("file:///D:/programming/new_spark/src/main/resources/people.csv").write.insertInto("default.people2")
//    println(spark.read.table("default.people2").count)

  }
saveAstable 和 insertInto 的区别是

spark中 提供了两种写入hive的模式,分别是:saveAsTable和insertInto。

saveAstable方法: 当mode=overwrite时,如果表不存在则创建;如果存在会按照指定位置插入原有数据,那么又可分为以下两种情况:

i、如果df的schema的字段数与已存在的表的schema的字段数相同,那么不需要考虑名称是否一致,会在指定位置进行插入(这一点区别insertInto)

ii、如果df的schema的字段数与已存在的表的schema的字段数不相同,会撇弃原有的schema,并按照df的schema重新创建并插入。

当mode=append时,也有以下两种情况:

i、当df的schema与已存在的schema个数相同:DataFrame中的列顺序不需要与现有表的列顺序相同。ii、当df的schema与已存在的schema个数不同:会报错。 

insertInto方法:与saveAsTable 最大的区别就是要求表必须存在否则插入会报错,并且df的schema与目标表的schema字段个数要保持一致。

动态分区
def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession
      .builder()
      .appName("Java Spark Hive Example")
      .master("local[*]")
      // 开启动态分区
      .config("hive.exec.dynamic.partition", "true")
      // 如果是strict模式,那么必须带有静态分区字段
      .config("hive.exec.dynamic.partition.mode", "nonstrict")
      .enableHiveSupport()
      .getOrCreate()

    val data = Seq(("Alice", 2), ("Bob", 311), ("Charlie", 35))
    val df = spark.createDataFrame(data).toDF("name", "age")

    // 使用DSL创建分区表并导入数据
df.write.partitionBy("age").mode(SaveMode.Overwrite).saveAsTable("ds_demo.mytable")
    // 使用DSL创建动态分区表要添加一下参数,不加参数回覆盖整个分区
    spark.sql("set spark.sql.sources.partitionOverwriteMode = DYNAMIC")
    // 往里追加数据
    df.write.mode(SaveMode.Append).insertInto("ds_demo.mytable")

    // 使用sql创建分区表
    spark.sql("create table ds_demo.mytable(name string) partitioned by (age int)")
    // 创建临时表当作数据源
    df.createTempView("tmp_view1")

    spark.sql("set spark.sql.sources.partitionOverwriteMode = DYNAMIC")
    // 动态分区字段必须放到最后
    spark.sql("insert overwrite table ds_demo.mytable partition(age) select name,age from tmp_view1")
  }
Spark 和Hive关于parquet格式的一个小问题

在较新版本的Spark中,默认情况下,Parquet文件使用一种新的、更高效的格式进行编写。这种新格式提供了更好的性能和功能,包括更好的压缩率和更好的谓词下推等,另外新的Parquet格式通常可以实现更高的压缩率,从而减少存储空间的占用。这对于大规模数据处理和存储来说非常重要。

但是新的Parquet格式可能不被旧版本的Hive完全支持。如果需要与旧版本的Hive或其他不支持新格式的工具进行兼容,可能需要使用旧的Parquet格式,那么就需要设置参数:spark.sql.parquet.writeLegacyFormat=true。

Hive支持新格式的Parquet从版本1.2.0开始。在Hive 1.2.0及更高版本中,可以使用新的Parquet格式进行数据的读取和写入操作。这些新的功能和优化使得Hive能够更好地与Parquet文件进行交互,并发挥Parquet列式存储的优势。

JDBC 数据源

JDBC 概述

Spark SQL 附带了一个用 JDBC 从其他数据库读取数据的数据源 API。它简化了查询这些数据源的方式,因为其返回的是 DataFrame,所以可以获得 Spark SQL的全部优势(包括性能方面以及与其他数据源的表进行连接的能力)

mysql数据源读取和写入

首先在IDEA中需要添加如下的依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.34</version>
</dependency>
 JDBC 的读取与写入及常见的配置选项
    // mysql -h IP地址 -P 3306 -u user -ppwd
    val mysqlUrl = "jdbc:mysql://IP地址:3306/test?useSSL=false"
    val mysqlDriver = "com.mysql.jdbc.Driver"
    val mysqlTableName = "stud"
    val mysqlUser = "user"
    val mysqlPassword = "pwd"

    val custerSchema = "id DECIMAL(38, 0), name3 STRING"


    // 加载数据源
    val jdbcDF = spark.read.format("jdbc")
      .option("url", mysqlUrl) //&useUnicode=true
      .option("driver", mysqlDriver)
      // Both 'dbtable' and 'query' can not be specified at the same time.
      .option("dbtable", mysqlTableName)
      .option("user", mysqlUser)
      .option("password", mysqlPassword)
      // Spark 2.4.0开始的Spark SQL的JDBC属性里才有query属性。
     // .option("query", "select name,seq from ke_p_role")

      // 用于从JDBC连接器读取数据的自定义模式,也可以指定部分字段,其他字段使用默认类型映射。
      // 其中列名应该与JDBC表的相应列名相同,不相同的列名不会生效
      // 用处:用户可以指定Spark SQL的相应数据类型,而不必使用默认值。
      // .option("customSchema",custerSchema)
      .load()
    jdbcDF.show()

    // 将读到的stud 创建新表写入 stud2
    jdbcDF.write.format("jdbc")
      .option("url", mysqlUrl)
      .option("user", mysqlUser)
      .option("password", mysqlPassword)
      .option("driver", mysqlDriver)
      // 如果表不存在可以创建一张新表
      .option("dbtable", "stud2")
      .mode("overwrite")
      .save
spark JDBC的案例之同步mysql数据到hive中
 val spark: SparkSession = SparkSession
    .builder()
    .appName("Java Spark Hive Example")
    .master("local[*]")
    // 开启动态分区
    .config("hive.exec.dynamic.partition", "true")
    // 如果是strict模式,那么必须带有静态分区字段
    .config("hive.exec.dynamic.partition.mode", "nonstrict")
    .enableHiveSupport()   // enableHiveSupport hive支持
    .getOrCreate()

  val mysqlDF = spark.read.format("jdbc")
    .option("url", "jdbc:mysql://IP地址:3306/test?useSSL=false") //&useUnicode=true
    .option("driver", "com.mysql.jdbc.Driver")
    .option("dbtable", "stud")
    .option("user", "user")
    .option("password", "pwd")
    .load()

  // 将读取的mysql表数据存放到临时表stu
  mysqlDF.createTempView("stu")
  //  在hive中创建一个表
  spark.sql("create table ds_demo.stu(name string) partitioned by (age int)")
  // 动态分区字段必须放到最后
  spark.sql("insert overwrite table ds_demo.stu partition(age) select name,age from stu")

;