2020.11.19(整合hive的metastore搭建)

简单回顾下:现在在讲spark-sql,以SQL为中心的,面向SQL,spark做了很多努力,比如给我们提供了DataSet以及它的子集DataFrame,DataSet既可以按照collection的风格处理数据集,而且也面向SQL增加了可以处理SQL的api,这节课先强化一下这些知识点。
比如整合api的,依然是通过api的方式得到了一个session,session是spark-sql里非常重要的概念,有了session之后怎么能得到一个数据集?
比如通过List,里面放一些元素:

import session.implicits._
val dataDF: DataFrame = List(
  "hello world"
  , "hello java"
  , "hello scala"
  , "hello java"
  , "hello scala"
).toDF("line")
)

看它的方法有没有.toDF的方法,没有但是不要紧,有一个隐式转换之前说过,只要加入import session.implicits._ 如果在公司想测一下代码逻辑的时候,就可以通过这样的方式.toDS或者.toDF,toDF有一个好处就是可以给它加一些名称/列名。toDS就是一个不带列名的数据集。
我们定义的List里面都是一个一个独立的元素,可以起名叫line,给它toDF,现在得到了一个dataFrame,拿这个dataFrame做什么事情,谈到大数据都会聊一个类似于hello world的案例,是单词统计wordCount,面向这个数据集,如果想以SQL的方式做word count,应该知道hive里面有相应的实现方式,SQL语句可以实现这件事情:
如果要写SQL的话,必须要把一个dataFrame注册到catalog里

 dataDF.createTempView("ooxx")
 val df: DataFrame = session.sql("select * from ooxx")
 df.show()
 df.printSchema()

原始表结果:

+-----------+
|       line|
+-----------+
|hello world|
| hello java|
|hello scala|
| hello java|
|hello scala|
+-----------+

root
 |-- line: string (nullable = true)

sql只是一个方法,里面的sql语句处理完之后最终也是变成一个dataFrame,所有这个dataFrame就会有show方法.
如果想做单词统计的话,最终的结果是什么样的?最终会有一张结果表,里面会有单词:hello,个数:5 。牵扯到一个问题,首先这些单词都混在一起,第一件是要把它切割,是要生成两列呢还是做扁平化。
第一件事:把单词拆解开,最好每个单词是一行

//第一步通过split函数对line根据空格进行切割,切完之后会得到一个集合
//通过explode函数将这个集合里所有元素,每个元素独立成一行和flatMap展开一样
//这个查询得到一个结果,把ooxx这张表每个单词一行
//定义完之后,把查询的结果,这张虚表,列的名字改一下,改成word
session.sql("select explode(split(line,' ')) as word from ooxx").show()

输出结果:

+-----+
| word|
+-----+
|hello|
|world|
|hello|
| java|
|hello|
|scala|
|hello|
| java|
|hello|
|scala|
+-----+

有一些需求的时候,一句SQL可能解决不了,先写第一句SQL。原始表只有一列,里面有好多复杂的数据,就用了split切割,再用explode把字符串打散,得到第二张表,做单词统计是在太简单了,就是一个groupBy和count的过程
接着写SQL,查询的结果不算完只是一个中间态,所以它作为了一个子查询,给一个别名叫tt因为要写另外一个查询。根据子查询里tt表里word列做分组,分组完之后我要查询的结果有哪些列?word列,原始单词要出来,还有一个count(*) 统计一下,一共有两列出现,单词和每个单词出现的个数。

select  word,count(*) from   (select explode(split(line,' ')) as word from ooxx) as tt   group by tt.word

输出结果:

+-----+--------+
| word|count(1)|
+-----+--------+
|hello|       5|
|scala|       2|
|world|       1|
| java|       2|
+-----+--------+

用一个SQL语句可以解决这件事情。这是第一个版本。
如果不用写SQL语句,用api的方式能不能完成?凸显一下dataSet的概念,有rdd了为什么还要出现dataSet?

//selectExpr()带表达式的,不止要查询列,还要有一个函数表达式
//作用在的一个数据集上,调用的一个select查询,然后给出了select后面相应的表达式,作用在这个数据集上就约等于完成了from table
//只不过没有去注册,就直接在数据集上触发逻辑了,这个很重要,查询完的结果是一个子表subTable
val subTab: DataFrame = dataDF.selectExpr("explode(split(line,' ')) as word")

面向api的时候,df相当于from table

//这个并不是想要的结果,就是把一行变成多行拆开,变成一个子表
//对这个子表再做一个groupBy和count统计的事情
//隐形的刚才的子表上from subTab做一个groupBy 但是先看返回值类型RelationalGroupedDataset
//如果使用它的时候,可以点出来的方法就是groupBy之后的max min count 等等一些计算,不是一个传统的dataSet
val dataset: RelationalGroupedDataset = subTab.groupBy("word")
val res: DataFrame = dataset.count()  //这时候才是结果,一个传统纯正的dataSet,这样的api完成上面sql的事情
res.show()   //逻辑都一样的,两种写法风格

进一步简写:

//这个写法可以再缩写,因为查出的子查询完全可以外挂,先是发生子查询,然后再分组,之后再统计,结果,再show,效果一样
dataDF.selectExpr("explode(split(line,' ')) as word").groupBy("word").count().show()

要找到的感觉:
SQL里面的select有select对应,groupby有groupBy对应,count有count对应,标准SQL都整出来了,自定义函数后面再说,dataSet其实可以支持collection数据集的操作,也可以支持SQL风格的操作。
但还要多想一件事情,以上两种方式,哪个更快一些??为什么是第二种??
上面先接收一个字符串,然后做语法解析,分词,关键字,等等之后才能操作,下面比上面做的少很多了,但是体感不明显。

真正到公司工作的时候,数据源也好,保存结果都趋向于稳定,因为常规的话,大家都会总结出数据以什么样的文件格式,什么样的载体最合适,来的数据源可能纯文本的,纯文本的方式也说了可以转成dataSet然后再去做转换得到dataFrame,第一版数据源到了要尽量给它中间状态转成最合适的格式,比如上面写的我不show()了,把结果拿下来,把它持久化写到磁盘上去。从上面session有read,输出的时候面向dataFrame和dataSet有write相呼应
可以存成csv,jdbc,json,orc,parquet,text很灵活可以存成各种格式,合适点的是parquet

res.write.modo(SaveMode.Append)parquet("./out/ooxx")  //也是文件,但是对里面的数据二进制的存储格式有自己的编解码的过程
//write写的时候有一个mode  有这么几种:Append追加 ErrorIfExists如果存在报错 Ignore忽略 Overwrite重写  非常方便  追加的话会得到两次加起来全量的数据

可以得到一个parquet文件,执行结束没有打印存成文件了,之后还可以放到hdfs的。
上面csc校验,下面是文件。因为parquet属于二进制文件,人类不可读的,会启用压缩一些算法,且带自描述,又能够存数据。但是看着看着会出现元数据,数据的内容结构体。既然可以做这件事情,代表了session.read()读的时候,读到的是一个parquet,给出相应路径,这种文件就有文件格式描述,得到的是dataFrame,既有文件数据又有列名,frame.show()和frame.printSchema()只需要拿到样例数据看一眼就知道数据结构了,操作起来比较简单。

val frame: DataFrame = session.read.parquet("./out/ooxx")
frame.show()
frame.printSchema()

这时候parquet文件就可读可写了,而且parquet这种格式是在公司常用的格式。
基于文件的形式读写的数据源有哪些做个总结:

/*session.read.parquet()
session.read.textFile()
session.read.json()
session.read.csv()
res.write.parquet()
res.write.orc()
res.write.text()
*/

注意读取任何格式的数据源都要转换成dataFrame
在企业中有一些数据除了存在文本里,有些数据还存在数据库里,spark-sql能不能把数据从数据库里拉取过来计算,我不希望把这些数据先变成文件上传到集群再发生计算,我就希望数据移动到集群里发生计算,要先解决一个问题,数据库在哪,哪张表长什么样子,想访问它一定会接入一个东西jdbc驱动要先准备。先去准备数据库那边的事情,再去说spark怎么对已经有的数据库里的数据进行操作。

yum install mysql-server   //默认5.1.73的版本
service mysqld start       //启动
在首次启动的时候会看到一大段描述,其中有一段命令:
mysql_secure_installation       //回车会走一个向导
输入current password            //当前密码,目前没有就回车
set root password?[Y/N] Y       //是否设置密码
New password:  					//设置密码
Remove anonymous users?[Y/N] n  //删除其他用户
Disallow root login remotely?[Y/n]  n      //是否禁用root从远端登录
Remove test database and access to it?  n  //是否移除测试库
Reload privilege tables now?  Y            //刷新权限的表
Thanks for using MySQL!

再登陆MySQL的时候:

mysql -uroot -pok   //输入用户密码就能登陆了

但此时要给root用户再赋权,可以再任何的位置去登陆

show databases;
use mysql
show tables;    //此时会看到一个user表
select user,host,password from user;
使用grant赋权,这个表里的东西是可以删掉的:
delete from user ;
help grant;    //会给出grant所有的语法格式和常用使用形式
grant all on *.* to 'root'@'%' identified by 'ok' with grant option; 
flush privileges;  //刷新权限

以上完成了数据库的安装
准备一个spark的库:

create database spark;
use spark;
create table ooxx(name varchar(100),age int);
show tables;
//插入多笔数据:values(),(),();   插入一笔数据:value();
insert into ooxx values("zhangsan",18),("lisi",22),("wangwu",99);  
剩下的就是spark如何拉取和查询数据

首先maven里还差MySQL的jar包:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.43</version>
</dependency>

jar包和数据库都准备好了

//还可以加载数据库里的数据变成dataFrame,里面有参数 表名 连接的属性
//表名要自定义给出,上面信息可以复用,但是每次每张表生成一个dataframe
//在没有hive的情况下,无论什么数据源拿到的都是DS或DF
val jdbcDF: DataFrame = ss.read.jdbc(pro.get("url").toString,"ooxx",pro)

如果拿到的是DataFrame的话,在catalog里是没有元数据的。

//更倾向于注册成表,未来期望直接写SQL来查询
jdbcDF.createTempView("ooxx")
ss.sql("select * from ooxx").show()

要以数仓或hive元数据为数据中心,先完成外部数据的加载,所有数据都是通过hive或spark那里,借助DML语句完成元数据的构建之后再写SQL,可能就不怎么去碰api的东西,因为太麻烦了。
写的时候也可以向外去写:

jdbcDF.write.jdbc(pro.get("url").toString,"xxxx",pro)

写的时候已经完成了创建表的过程。虽然可以向数据库里去写数据,但不是太推荐,因为在跨系统的时候,跨技术的时候,数据虽然可以给到对方,但是成本太高了,风险也多,在大点的公司基本是以文件的方式游走,数据库会卸数成文件,通过中转站load到数据库里,因为是文本批量的数据,一块速度相对快点,MySQL回写也一样,落成文件,scv等格式分隔符是否有规定,把文件中转到数据库的运维,跑批跑作业,把它load加载到数据库里,不会用面向网线连接把大批量数据有走来游走去,可靠稳定。
再创建两张表:

create table users (id int, name varchar(100), age int);
create table score (id int, name varchar(100), score int);
insert into users values (1,"zhangsan",18),(2,"lisi",22),(3,"wangwu",33);
insert into score values(1,"zhangsan",100),(2,"lisi",99),(4,"zhaoliu",59);

读取

val userDF: DataFrame = ss.read.jdbc(pro.get("url").toString,"users",pro)
val scoreDF: DataFrame = ss.read.jdbc(pro.get("url").toString,"score",pro)
userDF.createTempView("userstab")
scoreDF.createTempView("scoretab")
val resDF: DataFrame = ss.sql("select  userstab.id,userstab.name,userstab.age,scoretab.score  from userstab join scoretab on userstab.id=scoretab.id")
resDF.show()
resDF.printSchema()
//重点是把最后结果写回MySQL
resDF.write.jdbc(pro.get("url").toString,"xxxx",pro)

这两次向回写有什么不一样的地方?
INFO DAGScheduler: Submitting 200 missing tasks from ResultStage 17 (MapPartitionsRDD[27] at jdbc at lesson03_sql_jdbc.scala)
一大半的时间都在执行这200个任务,task数量是由分区数决定的,怎么解决这个问题?
重新设置分区数:

println(resDF.rdd.partitions.length)
val resDF01: Dataset[Row] = resDF.coalesce(1)
println(resDF01.rdd.partitions.length)

任务数量变少了,开始的200数量是哪里来的?有一个属性

val ss: SparkSession = SparkSession
  .builder()
  .appName("test")
  .master("local[*]")
  .config("spark.sql.shuffle.partitions", "1")  //默认会有200并行度,如果数据量很大会按数据量的并行度,最小值会按照200,所有一般都会去调整这个值,是shuffle的并行度和jdbc没有关系
  .getOrCreate()

INFO DAGScheduler: Submitting 1 missing tasks from ShuffleMapStage 4 (MapPartitionsRDD[21] at jdbc at lesson03_sql_jdbc.scala)

shuffle和分区器还有分区的数量有关系。不满足都相同的情况下,就会走一次shuffle。

override def getDependencies: Seq[Dependency[_]] = {
  rdds.map { rdd: RDD[_] =>
    if (rdd.partitioner == Some(part)) {
      logDebug("Adding one-to-one dependency with " + rdd)
      new OneToOneDependency(rdd)
    } else {
      logDebug("Adding shuffle dependency with " + rdd)
      new ShuffleDependency[K, Any, CoGroupCombiner](
        rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer)
    }
  }
}

会判断是窄依赖还是宽依赖是否要进行一次shuffle,凡是有shuffle的都可能调优没有shuffle。

@Experimental
def combineByKeyWithClassTag[C](
    createCombiner: V => C,
    mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C,
    partitioner: Partitioner,
    mapSideCombine: Boolean = true,
    serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)] = self.withScope {
  require(mergeCombiners != null, "mergeCombiners must be defined") // required as of Spark 0.9.0
  if (keyClass.isArray) {
    if (mapSideCombine) {
      throw new SparkException("Cannot use map-side combining with array keys.")
    }
    if (partitioner.isInstanceOf[HashPartitioner]) {
      throw new SparkException("HashPartitioner cannot partition array keys.")
    }
  }
  val aggregator = new Aggregator[K, V, C](
    self.context.clean(createCombiner),
    self.context.clean(mergeValue),
    self.context.clean(mergeCombiners))
  if (self.partitioner == Some(partitioner)) {
    self.mapPartitions(iter => {
      val context = TaskContext.get()
      new InterruptibleIterator(context, aggregator.combineValuesByKey(iter, context))
    }, preservesPartitioning = true)
  } else {
    new ShuffledRDD[K, V, C](self, partitioner)
      .setSerializer(serializer)
      .setAggregator(aggregator)
      .setMapSideCombine(mapSideCombine)
  }
}

如果上游的分区数量和下游分区数量一样,上游的还是走过shuffle有分区器的,就不会再走shuffle,每条数据还是自己的分区号。
上节课演示过了,DQL查询语句是不支持的,因为缺少了hive的支持,catalog只是编目,把DF能够映射成一个元数据供查询就可以了,这个DataFrame是通过api来的,而不是DDL创建的,这节课如果想让spark-sql支持其他的DDL语句创建表的操作,而且下次还能够拿到这些数据的话,就需要存储的能力,要开启hivesupport

现在单机的也不启动任何hive的东西,只是做一件事情,把hive的方法开启:

import org.apache.spark.SparkContext
import org.apache.spark.sql._

object lesson04_sql_stangalone_hive {
  def main(args: Array[String]): Unit = {
    val ss: SparkSession = SparkSession
      .builder()
      .appName("test")
      .master("local[*]")
      .config("spark.sql.shuffle.partitions", "1")
      .enableHiveSupport()     //开启hive支持
      .getOrCreate()
    val sc: SparkContext = ss.sparkContext
    sc.setLogLevel("ERROR")

    ss.sql("create table oxox(name string,age int)")  //hive的query language  HQL语法
    ss.catalog.listTables().show()
  }
}

第一个报错:我写的spark程序,却提示没有找到hive相应的东西,在依赖中添加hive相关的

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-hive_2.11</artifactId>
    <version>2.1.1</version>
</dependency>

只需要把依赖添加进去再运行,就不报错了,ListTable里就有了oxox这张表

-chgrp: 'LAPTOP-CPJCKFQR\chaokeaimuzhi' does not match expected pattern for group
Usage: hadoop fs [generic options] -chgrp [-R] GROUP PATH...
-chmod: chmod : mode '0' does not match the expected pattern.
Usage: hadoop fs [generic options] -chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...
+----+--------+-----------+---------+-----------+
|name|database|description|tableType|isTemporary|
+----+--------+-----------+---------+-----------+
|oxox| default|       null|  MANAGED|      false|
+----+--------+-----------+---------+-----------+

表创建出来了,且可以managed管理了,第二次把建表语句注释掉,直接运行:

ss.catalog.listTables().show()

发现这张表还在:

+----+--------+-----------+---------+-----------+
|name|database|description|tableType|isTemporary|
+----+--------+-----------+---------+-----------+
|oxox| default|       null|  MANAGED|      false|
+----+--------+-----------+---------+-----------+

代表什么意思:第一次执行的时候,建表语句能通过了,因为补全了hive的东西,DDL创建的元数据也被持久化了。
持久化在哪里了?看项目工程的目录,出现了spark-warehouse和metastore_db,在spark-warehouse出现了一个目录oxox,在刚才的模式下,虽然集群没有搭建hive,但是只要开启了,会做一件事情,自己会启动hive的metastore,在hive中也可以让metastore不独立成为JVM,直接启动hiveserver2连接hive数据库,metastore是可以集成进其他进程的,现在就集成进spark里去了。metastore_db存放的持久化的数据。

既然warehouse可以定义,可以在配置当中写下属性,人为指定数仓建表的存放路径:
spark.sql.warehouse.dir d:/spark/warehouse 作为未来存放建表的地方

ss.catalog.listTables().show                //作用在current库
sql("create database kb09")
sql("create table table01(name string)")    //作用在current库
ss.catalog.listTables().show()              //作用在current库
println("------------------------------------------------")
sql("use kb09")
ss.catalog.listTables().show()               //作用在kb09库
sql("create table table02(name string)")     //作用在kb09库
ss.catalog.listTables().show()               //作用在kb09这个库

MySQL只是个软件,不代表数据库,数据库管理软件,一个MySQL可以创建很多的库database,库是隔离级别比较高的,每个库中都可以有一个user表,所以公司可以只装一个MySQL,不同的项目组自己用自己的库database,看似不值钱的理论,带入实际使用当中,spark和hive也是一样,都有库的概念,如何做规划的事情。刚进来的时候是在默认的库。

第一个显示库的时候能看到xxx,然后建了一个库kb09,之后建了一张表table01,再去打印,能看到xxxx和table01,所以table01又建在了默认的作用域,当前的默认库。然后切到了kb09库做了一个打印,现在库里没有表了,说明库变了,再去执行建表table02,因为做过切库了,所以current库变成了kb09,再去打印能看到新建的表。

输出结果:

+----+--------+-----------+---------+-----------+
|name|database|description|tableType|isTemporary|
+----+--------+-----------+---------+-----------+
|oxox| default|       null|  MANAGED|      false|
+----+--------+-----------+---------+-----------+

-chgrp: 'NT AUTHORITY\SYSTEM' does not match expected pattern for group
Usage: hadoop fs [generic options] -chgrp [-R] GROUP PATH...
-chgrp: 'LAPTOP-CPJCKFQR\chaokeaimuzhi' does not match expected pattern for group
Usage: hadoop fs [generic options] -chgrp [-R] GROUP PATH...
-chmod: chmod : mode '0' does not match the expected pattern.
Usage: hadoop fs [generic options] -chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...
+-------+--------+-----------+---------+-----------+
|   name|database|description|tableType|isTemporary|
+-------+--------+-----------+---------+-----------+
|   oxox| default|       null|  MANAGED|      false|
|table01| default|       null|  MANAGED|      false|
+-------+--------+-----------+---------+-----------+

------------------------------------------------

+----+--------+-----------+---------+-----------+
|name|database|description|tableType|isTemporary|
+----+--------+-----------+---------+-----------+
+----+--------+-----------+---------+-----------+

-chgrp: 'LAPTOP-CPJCKFQR\chaokeaimuzhi' does not match expected pattern for group
Usage: hadoop fs [generic options] -chgrp [-R] GROUP PATH...
+-------+--------+-----------+---------+-----------+
|   name|database|description|tableType|isTemporary|
+-------+--------+-----------+---------+-----------+
|table02|    kb09|       null|  MANAGED|      false|
+-------+--------+-----------+---------+-----------+

在这里不会再对hive做更多的复习,一定要注意,现在在学spark-sql,因为有完整的hive和HQL语句在前面hive讲过了,所以如果跳着听的话,一定要去复习hive和HQL语句,可以得出一个结论,现在只是学一个spark-sql on hive用到HQL语句,以后学impala都是会和hive的metastore,hive的HQL语句有一些交集的,整个大数据里最最最值钱的,出去上班90%会干的事情就是hive的HQL语句,这个不及格,不通过,DDL、DML、DQL还是DCL不通过,根本很难面试通过,进公司也会一脸懵逼。hive是大数据里最值钱的。

回头看视频,养成习惯看官网,LanguageManual
看看hive怎么搭建,hive的提交SQL有哪些方式,hiveserver2和hive命令行的方式,稍微回忆一下,下节课会把hive和metastore支起来,spark真正on hive的时候,元数据可以完全交给hive数仓,借用它的元数据,两边都能操作,可以看到相同的元数据,第二点,会从敲api,idea这个接口上,有没有其他的写sql语句的形式,以及spark里有没有一个类似于hiveserver2的服务,可以让公司所有人从远程直接提交一个sql语句过来执行。等等的spark-sql的使用形式下节课会讲,现在要先复习一下hive的使用形式。换言之,到公司了可能连idea这个builder的过程sparksession 的过程都没有,可能就是直接看到一个黑界面,和MySQL一样,完全提交给了spark自己,还可以用jdbc远程连接过去。它也有一个thrift的server。
会讲原理、知识点、企业级的使用方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值