SparkSQL 整合 Hive

Hive 是一个外部的数据存储和查询引擎, 所以如果 Spark 要访问 Hive 的话, 就需要先整合 Hive。

整合什么 ?

  • MetaStore, 元数据存储
    SparkSQL 内置的有一个 MetaStore, 通过嵌入式数据库 Derby 保存元信息, 但是对于生产环境来说, 还是应该使用 Hive 的 MetaStore, 一是更成熟, 功能更强, 二是可以使用 Hive 的元信息。
  • 查询引擎
    SparkSQL 内置了 HiveSQL 的支持, 所以无需整合。

为什么要开启 Hive 的 MetaStore?

Hive 的 MetaStore 是一个 Hive 的组件, 一个 Hive 提供的程序, 用以保存和访问表的元数据, 整个 Hive 的结构大致如下:

hivesql和sparksql的区别 hivesql和sparksql使用场景_Hive

由上图可知道, 其实 Hive 中主要的组件就三个, HiveServer2 负责接受外部系统的查询请求, 例如 JDBC, HiveServer2 接收到查询请求后, 交给 Driver 处理, Driver 会首先去询问 MetaStore 表在哪存, 后 Driver 程序通过 MR 程序来访问 HDFS 从而获取结果返回给查询请求者。

而 Hive 的 MetaStore 对 SparkSQL 的意义非常重大, 如果 SparkSQL 可以直接访问 Hive 的 MetaStore, 则理论上可以做到和 Hive 一样的事情, 例如通过 Hive 表查询数据。

而 Hive 的 MetaStore 的运行模式有三种:

  • 内嵌 Derby 数据库模式
    这种模式不必说了, 自然是在测试的时候使用, 生产环境不太可能使用嵌入式数据库, 一是不稳定, 二是这个 Derby 是单连接的, 不支持并发。
  • Local 模式
    Local 和 Remote 都是访问 MySQL 数据库作为存储元数据的地方, 但是 Local 模式的 MetaStore 没有独立进程, 依附于 HiveServer2 的进程。
  • Remote 模式
    和 Local 模式一样, 访问 MySQL 数据库存放元数据, 但是 Remote 的 MetaStore 运行在独立的进程中。

我们显然要选择 Remote 模式, 因为要让其独立运行, 这样才能让 SparkSQL 一直可以访问.

一、Hive 开启 MetaStore

Step 1: 修改 hive-site.xml

<!--默认创建表存放的位置-->
    <property>
      <name>hive.metastore.warehouse.dir</name>
      <value>/user/hive/warehouse</value>
    </property>
	<!--指定mysql中hive数据库的访问路径,如果不存在,会创建-->
    <property>
      <name>javax.jdo.option.ConnectionURL</name>
      <value>jdbc:mysql://node01:3306/hive?createDatabaseIfNotExist=true</value>
    </property>
	<!--指定连接数据库的驱动-->
    <property>
      <name>javax.jdo.option.ConnectionDriverName</name>
      <value>com.mysql.jdbc.Driver</value>
    </property>
	<!--指定连接数据库的用户名 root-->
    <property>
      <name>javax.jdo.option.ConnectionUserName</name>
      <value>username</value>
    </property>
	<!--指定连接数据库的密码 root-->
    <property>
      <name>javax.jdo.option.ConnectionPassword</name>
      <value>password</value>
    </property>
	<!-- 如果要运行在一个服务上,可以配置,告诉他,metastore不是本地运行的 -->
    <property>
      <name>hive.metastore.local</name>
      <value>false</value>
    </property>
	<!-- 指定 hive metastore 服务请求的 uri 地址 --> 
    <property>
      <name>hive.metastore.uris</name>
      <value>thrift://node01:9083</value>  //当前服务器
    </property>

以上配置文件中的参数如果没有的添加上即可

Step 2: 启动 Hive MetaStore(nohup命令后台启动)

nohup /export/servers/hive/bin/hive --service metastore 2>&1 >> /var/log.log &

二、SparkSQL 整合 Hive 的 MetaStore

即使不去整合 MetaStore, Spark 也有一个内置的 MateStore, 使用 Derby 嵌入式数据库保存数据, 但是这种方式不适合生产环境, 因为这种模式同一时间只能有一个 SparkSession 使用, 所以生产环境更推荐使用 Hive 的 MetaStore。

SparkSQL 整合 Hive 的 MetaStore 主要思路就是要通过配置能够访问它, 并且能够使用 HDFS 保存 WareHouse, 这些配置信息一般存在于 Hadoop 和 HDFS 的配置文件中, 所以可以直接拷贝 Hadoop 和 Hive 的配置文件到 Spark 的配置目录

cd /export/servers/hadoop/etc/hadoop
cp hive-site.xml core-site.xml hdfs-site.xml /export/servers/spark/conf/   

scp -r /export/servers/spark/conf node02:/export/servers/spark/conf
scp -r /export/servers/spark/conf node03:/export/servers/spark/conf

Spark 需要 hive-site.xml 的原因是, 要读取 Hive 的配置信息, 主要是元数据仓库的位置等信息
Spark 需要 core-site.xml 的原因是, 要读取安全有关的配置
Spark 需要 hdfs-site.xml 的原因是, 有可能需要在 HDFS 中放置表文件, 所以需要 HDFS 的配置

注意事项:如果不希望通过拷贝文件的方式整合 Hive, 也可以在 SparkSession 启动的时候, 通过指定 Hive 的 MetaStore 的位置来访问, 但是更推荐整合的方式。
特点:只有使用SparkShell或者SparkSubmit的时候才能享受这个整合的便利

三、访问 Hive 表

3.1 准备-----在 Hive 中创建表

第一步, 需要先将文件上传到集群中, 使用如下命令上传到 HDFS 中:

hdfs dfs -mkdir -p /dataset
hdfs dfs -put studenttabl10k /dataset/

第二步, 使用 Hive 或者 Impala 执行如下 SQL

CREATE DATABASE IF NOT EXISTS spark01;

USE spark01;

CREATE EXTERNAL TABLE student
(
  name  STRING,
  age   INT,
  gpa   string
)
ROW FORMAT DELIMITED
  FIELDS TERMINATED BY '\t'
  LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/dataset/hive';

LOAD DATA INPATH '/dataset/studenttab10k' OVERWRITE INTO TABLE student;

studenttab10k 文件数据可以自行模拟,字段是student表字段。

3.2 通过 SparkSQL 查询 Hive 的表

通过 SparkSQL 可以直接创建 Hive 表, 并且使用 LOAD DATA 加载数据,通过spark-shell命令进入命令行界面 Spark session available as ‘spark’.

val createTableStr =
  """
    |CREATE EXTERNAL TABLE student
    |(
    |  name  STRING,
    |  age   INT,
    |  gpa   string
    |)
    |ROW FORMAT DELIMITED
    |  FIELDS TERMINATED BY '\t'
    |  LINES TERMINATED BY '\n'
    |STORED AS TEXTFILE
    |LOCATION '/dataset/hive'
  """.stripMargin

spark.sql("CREATE DATABASE IF NOT EXISTS spark02")
spark.sql("USE spark02")
spark.sql(createTableStr)
spark.sql("LOAD DATA INPATH '/dataset/studenttab10k' OVERWRITE INTO TABLE student")
spark.sql("select * from student limit 100").show()

目前 SparkSQL 支持的文件格式有 sequencefile, rcfile, orc, parquet, textfile, avro, 并且也可以指定 serde 的名称。

3.3 使用 SparkSQL 处理数据并保存进 Hive 表

前面都在使用 SparkShell 的方式来访问 Hive, 编写 SQL, 通过 Spark 独立应用的形式也可以做到同样的事, 但是需要一些前置的步骤, 如下

Step 1: 导入 Maven 依赖

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-hive_2.11</artifactId>
    <version>${spark.version}</version>
<dependency>

Step 2: 配置 SparkSession

如果希望使用 SparkSQL 访问 Hive 的话, 需要做两件事

  1. 开启 SparkSession 的 Hive 支持(必须),经过这一步配置, SparkSQL 才会把 SQL 语句当作 HiveSQL 来进行解析
  2. 设置 WareHouse 的位置(必须),虽然 hive-stie.xml 中已经配置了 WareHouse 的位置, 但是在 Spark 2.0.0 后已经废弃了 hive-site.xml 中设置的 hive.metastore.warehouse.dir, 需要在 SparkSession 中设置 WareHouse 的位置
  3. 设置 MetaStore 的位置
val spark = SparkSession
  .builder()
  .appName("hive example") 
  // 设置 WareHouse 的位置
  .config("spark.sql.warehouse.dir", "hdfs://node01:8020/dataset/hive") 
  // 设置 MetaStore 的位置
  .config("hive.metastore.uris", "thrift://node01:9083")  
  // 开启 Hive 支持               
  .enableHiveSupport()                                                   
  .getOrCreate()

配置好了以后, 就可以通过 DataFrame 处理数据, 后将数据结果推入 Hive 表中了, 在将结果保存到 Hive 表的时候, 可以指定保存模式 SaveMode(SaveMode是指定保存模式,是枚举类:Append、Overwrite、ErrorIfExists、Ignore)

val schema = StructType(
  List(
    StructField("name", StringType),
    StructField("age", IntegerType),
    StructField("gpa", FloatType)
  )
)

val studentDF = spark.read
  .option("delimiter", "\t")
  .schema(schema)
  .csv("dataset/studenttab10k")

val resultDF = studentDF.where("age < 50")

// 通过 mode 指定保存模式, 通过 saveAsTable 保存数据到 Hive
resultDF.write.mode(SaveMode.Overwrite).saveAsTable("spark_integrition1.student")

3.4 使用IDEA来举例

object HiveAccess {
  def main(args: Array[String]): Unit = {
    // 1.创建SparkSession
    // 1.2 指定Metastore的位置
    // 1.3 指定Warehouse的位置
    val spark = SparkSession.builder()
      .appName("hive_example")
      // 1.1 开启Hive支持
      .enableHiveSupport()
      // thrift是RPC框架,协议
      .config("hive.metastore.uris","thrift://node01:9083")
      .config("spark.sql.warehouse.dir","hdfs://node01:8020/dataset/hive")
      .getOrCreate()
    import spark.implicits._
    // 2.读取数据
    // 2.1 上传HDFS,因为要在集群中执行,没办法保证程序在哪个机器中执行,所以,要把文件上传到所有的机器中,才能读取本地文件
    // 上传到HDFS就可以解决这个问题,所有的机器都可以读取HDFS中的文件,它是一个外部系统
    // 2.2 使用DF读取数据
    val schema: StructType = StructType(
      List(
        StructField("name", StringType),
        StructField("age", IntegerType),
        StructField("gpa", FloatType)
      )
    )

    val df: DataFrame = spark.read
      .option("delimiter", "\t")
      .schema(schema)
      .csv("hdfs://node01:8020/dataset/studenttab10k")

    val resultDF: Dataset[sql.Row] = df.where('age > 50)


    // 3.写入数据,使用写入表的API,saveAsTable
    resultDF.write
      .mode(SaveMode.Overwrite)
      .saveAsTable("spark02.student")
  }
}