第一章:
一.介绍
Spark是基于内存的迭代计算
四.Local模式
仅仅本机运行
Local[k]代表有几个线程在跑
Local[*]代表跑满
五.spark使用
1.bin/spark-submit 参数,可以用来提交任务
参数如下
--master 指定Master的地址,默认为Local --class: 你的应用的启动类 (如 org.apache.spark.examples.SparkPi) --deploy-mode: 是否发布你的驱动到worker节点(cluster) 或者作为一个本地客户端 (client) (default: client)* --conf: 任意的Spark配置属性, 格式key=value. 如果值包含空格,可以加引号“key=value” application-jar: 打包好的应用jar,包含依赖. 这个URL在集群中全局可见。 比如hdfs:// 共享存储系统, 如果是 file:// path, 那么所有的节点的path都包含同样的jar application-arguments: 传给main()方法的参数 --executor-memory 1G 指定每个executor可用内存为1G --total-executor-cores 2 指定每个executor使用的cup核数为2个
执行如下
bin/spark-submit --class org.apache.spark.examples.SparkPi --executor-memory 1G --total-executor-cores 2 ./examples/jars/spark-examples_2.11-2.1.1.jar 100
2.bin/spark-shell,进入命令行环境,默认很多东西会创建好,比如sc变量
jsp命令查看java运行的程序
spark-shell提示的,网址,比如hadoop102:4040,是查看网页版的程序运行状态器,即Spark Jobs
yarn application -list,查看应用id
六.WordCount程序
1.load(textFile)
2.flat(flatMap)
3.group(groupBy或者map为元组)
4.聚合(reduceByKey)
5.打印(collect)
6.停止(sc.stop)
七.Idea开发
1.maven项目
2.引入spark依赖
3.添加build段落
4.新建scala,标记为source dir
5.新建scala文件。因为是maven build有引入,所以可以自动添加scala标记
6.new SparkContext,创建context
7.new SparkConf,创建配置,有一系列set方法
八.Yarn部署
yarn-client:有交互
yarn-cluster:无交互
区别是driver程序要哪里执行,运行节点
需要修改以下配置文件
需要启动HDFS和Yarn集群
spark-submit要添加以下参数
bin/spark-submit --class org.apache.spark.examples.SparkPi --master yarn --deploy-mode client ./examples/jars/spark-examples_2.11-2.1.1.jar 100
日志查看:
1.修改配置文件spark-defaults.conf
spark.yarn.historyServer.address=hadoop102:18080 spark.history.ui.port=18080
2.重启历史服务
sbin/stop-history-server.sh stopping org.apache.spark.deploy.history.HistoryServer sbin/start-history-server.sh starting org.apache.spark.deploy.history.HistoryServer, logging to /opt/module/spark/logs/spark-atguigu-org.apache.spark.deploy.history.HistoryServer-1-hadoop102.out
3.提交任务
bin/spark-submit --class org.apache.spark.examples.SparkPi --master yarn --deploy-mode client ./examples/jars/spark-examples_2.11-2.1.1.jar 100
4.去hadoop网页查看
spark-shell设置为master yarn模式
spark-shell --master yarn
spark程序打包:
使用以下maven插件
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.0.0</version> <configuration> <archive> <manifest> <mainClass>WordCount</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
双击package部分
文件路径查找:
默认从部署环境中查找
九.Standalone模式
Spark独立部署,不依赖其他大数据组件
xcall jps:在所有集群上运行jps
十.Driver和Executor关系
Driver:创建Spark应用上下文环境对象的程序,是个驱动器
Executor:接收任务并执行任务
Driver发送任务
Executor向Driver反馈任务执行情况
所有算子里的计算功能(参数函数)都是Executor执行
如果Executor里用到了Driver里的变量,这个变量要可以序列化,方便Driver传送这个变量给Executor
十一.历史服务
可以在网页上查看历史任务
十二.高可用HA
利用zookeeper实现高可用,编写配置文件即可
第二章.RDD
一.JAVA IO回顾
输入 输出
字节 字符
FileInputStream
BufferedInputStream
BufferedReader,InputStreamReader
二.RDD概述
RDD运算是装饰者模式,将数据处理逻辑封装,即对数据计算的抽象,不保留数据
可分区:适合并行计算
RDD属性:
1) 一组分区(Partition),即数据集的基本组成单位;
2) 一个计算每个分区的函数;
3) RDD之间的依赖关系;
4) 一个Partitioner,即RDD的分片函数;
5) 一个列表,存储存取每个Partition的优先位置(preferred location)。
依赖关系:血缘
移动数据不如移动计算
算子:
1.转换算子,进行数据变换
2.行动算子,触发计算,产生输出
二.RDD编程
1.RDD创建
从集合里:makeRDD,parallelize
从文件:sc.textFile
从其他RDD
保存到文件:rdd.saveAsTextFile
RDD分区规则用hadoopFile,分片规则完全一样
计算数据的分区与存储数据到分区是分开的
hadoopfs按行存储于读取
三.RDD转换算子
分为Value类型和Key-Value类型
四.Vaule类型转换算子
1.map(func)
返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
2.mapPartitions(func)
类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。
目的是将分区里的计算集中分给一个Executor,容易OOM内存不够用
3.mapPartitionsWithInex(func)
类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];
可以根据分区号追踪任务
4.flatMap(func)
类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
5.glom()
将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]
6.groupBy(func)
分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。返回对偶元祖
7.filter(func)
过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成
8.sample(withReplacement, fraction, seed)
以指定的随机种子随机抽样出数量为fraction的数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子
9.distinct([numTasks])
对源RDD进行去重后返回一个新的RDD。默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它
没有shuffle性能好
10.coalesce(numPartitions)
缩减分区数,用于大数据集过滤后,提高小数据集的执行效率
11.repatition(numPartitions)
根据分区数,重新通过网络随机洗牌所有数据
12.sortBy(func,[ascending], [numTasks])
使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序
分区数量改变,task改变,并行度度改变
13.pipe(command, [envVars])
管道,针对每个分区,都执行一个shell脚本,返回输出的RDD
五.双Value
两个RDD集合操作,交并差笛卡尔积zip等
六.kv算子
这些算子被调用,要求数据一定要是kv格式
1.partitionBy
对pairRDD进行分区操作,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区, 否则会生成ShuffleRDD,即会产生shuffle过程
2.groupBykey
groupByKey也是对每个key进行操作,但只生成一个sequence
3.reduceBykey
在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置
4.aggregateBykey
在kv对的RDD中,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出
5.foldBykey
aggregateByKey的简化操作,seqop和combop相同
6.combineBykey
对相同K,把V合并成一个集合。第一个值也是用函数计算出来。得到一个V的计算值
7.sortByKey
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
8.mapValues
针对于(K,V)形式的类型只对V进行操作
9.join
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
10.cogroup
在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
七.行动算子
把RDD里的数据当做一个数组来处理
1.reduce
通过func函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据
2.collect
在驱动程序中,以数组的形式返回数据集的所有元素
3.count
返回RDD中元素的个数
4.first
返回RDD中元素的个数
5.take
返回一个由RDD的前n个元素组成的数组
6.takeOrdered
返回该RDD排序后的前n个元素组成的数组
7.aggregate
aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致
8.fold
折叠操作,aggregate的简化操作,seqop和combop一样
9.saveAsTextFile
将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
10.saveAsSequenceFile
将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统
11.saveAsObjectFile
用于将RDD中的元素序列化成对象,存储到文件中
12.countByKey
针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数
13.foreach(func)
在数据集的每一个元素上,运行函数func进行更新
八.Task执行序列化
网络里传递的对象一定要序列化
九.RDD依赖关系
RDD里保存了依赖关系,出错了可以重来一遍
toDebugString:查看血缘
dependicies:查看依赖
窄依赖:窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女
宽依赖:宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle,总结:宽依赖我们形象的比喻为超生
十.DAG
有向无环图
DAG划分stage,宽依赖是划分Stage的依据
十一.任务划分
RDD任务切分中间分为:Application、Job、Stage和Task
十二.缓存
persist或者cache方法内存缓存
为的是存储耗时动作
十三.检查点
checkponit函数持久化,要先用setCheckoutDir设置
缓存和检查点适合长链计算,防出错
十四.RDD分区器
通过使用RDD的partitioner 属性来获取 RDD 的分区方式
Hash分区
HashPartitioner分区的原理:对于给定的key,计算其hashCode,并除以分区的个数取余,如果余数小于0,则用余数+分区的个数(否则加0),最后返回的值就是这个key所属的分区ID。
Ranger分区
HashPartitioner分区弊端:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据。
RangePartitioner作用:将一定范围内的数映射到某一个分区内,尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大,但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内
自定义分区
要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个方法。
(1)numPartitions: Int:返回创建出来的分区数。
(2)getPartition(key: Any): Int:返回给定键的分区编号(0到numPartitions-1)。
(3)equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同
使用自定义的 Partitioner 是很容易的:只要把它传给 partitionBy() 方法即可。Spark 中有许多依赖于数据混洗的方法,比如 join() 和 groupByKey(),它们也可以接收一个可选的 Partitioner 对象来控制输出数据的分区方式
十五.数据读取与保存
Spark的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。
文件格式分为:Text文件、Json文件、Csv文件、Sequence文件以及Object文件;
文件系统分为:本地文件系统、HDFS、HBASE以及数据库。
使用RDD读取JSON文件处理很复杂,同时SparkSQL集成了很好的处理JSON文件的方式,所以应用中多是采用SparkSQL处理JSON文件
读取mysql:引入mysql依赖,通过JdbcRDD访问
为避免连接数过多,可以以partition为单位操作数据库,可能OOM
读取hbase:引入hbase依赖,newAPIHadoopRDD创建
十六.累加器
Spark三大数据结构:
1.RDD:分布式数据集
2.广播变量:分布式只读共享变量
3.累加器:分布式只写共享变量
创建累加器:sc.longAccumulator
add,value
也可以自定义累加器
十七.广播变量
目的是提高效率,减少传输,传送大变量
创建:broadcast
第三章.SparkSql
一.概述
Spark SQL是Spark用来处理结构化数据的一个模块,它提供了2个编程抽象:DataFrame和DataSet,并且作为分布式SQL查询引擎的作用
Spark SQL是将SQL转换成RDD,然后提交到集群执行,执行效率非常快
HDFS:数据没有结构存储;数仓:数据结构化(加标签)分门别类存储
Spark SQL通过DataFrame和DataSet解决了RDD里的数据没有结构的问题
二.DataFrame于DataSet介绍
RDD是计算的抽象,DataFrame更近一步,是结构(Schema)的抽象,不过只包含数据表的字段名及给字段类型没有类ORM映射(只能在SQL语句里用,不能在后续程序取结果集的时候用) 。DataSet添加了类型静态编译检查,最新版本的DataFrame是Dataset[Row]的特例不能直接获取列的属性名,即ORM(把数据库的东西放进类里映射到相应属性),但区别是DataSet是以类来指定结构的,DataFram是一个个基本类型来指定结构的,即Row。
DataFrame没有类,那么获得的Row类型对象,只能用类似SQL获取结果集的方式->getInt(0)这样的方式取列的值,而不是直接用属性名(或者用case匹配)
RDD,增加结构信息->DataFrame,增加类->DataSet
三.DataFrame操作
SparkSession是Spark最新的SQL查询起始点
命令行里SparkSession的对象叫做spark
创建DataFrame方式:
1.通过Spark的数据源进行创建
spark.read.
csv format jdbc json load option options orc parquet schema table text textFile
2.从一个存在的RDD进行转换
3.还可以从Hive Table进行查询返回
df.show:打印数据集结果
语法风格:一种是传统SQL语句,一种是DSL以函数风格调用
SQL风格:
视图:只能查不能改
表:能查能改
createTempView:创建临时视图
createGlobalView:创建全局视图,访问的时候加全局限定名,修饰视图名
spark.sql:传入SQL语句
show:显示
DSL风格:
printSchema:打印表结构
select函数的参数即是列名,也是要计算的函数体语句
$"name":传参的时候,不会与后面的数据进行计算来得到列名。遍历每一行的时候,引用name列的值
好处一是编写简单,一个函数可以完成很多SQL语句,二是有类型检查
RDD与DataFrame转换
如果需要RDD与DF或者DS之间操作,那么都需要引入 import spark.implicits._ 【spark不是包名,而是sparkSession对象的名称】
手动转换:toDF("name","age"),转为DataFrame并制定Schema
反射转换:
(1)创建一个样例类
scala> case class People(name:String, age:Int)
(2)根据样例类将RDD转换为DataFrame
scala> peopleRDD.map{ x => val para = x.split(",");People(para(0),para(1).trim.toInt)}.toDF
res2: org.apache.spark.sql.DataFrame = [name: string, age: int]
就是RDD的范型要是一个类,toDF可以反射
编程方式:
(1)导入所需的类型
scala> import org.apache.spark.sql.types._
import org.apache.spark.sql.types._
(2)创建Schema
scala> val structType: StructType = StructType(StructField("name", StringType) :: StructField("age", IntegerType) :: Nil)
structType: org.apache.spark.sql.types.StructType = StructType(StructField(name,StringType,true), StructField(age,IntegerType,true))
(3)导入所需的类型
scala> import org.apache.spark.sql.Row
import org.apache.spark.sql.Row
(4)根据给定的类型创建二元组RDD
scala> val data = peopleRDD.map{ x => val para = x.split(",");Row(para(0),para(1).trim.toInt)}
data: org.apache.spark.rdd.RDD[org.apache.spark.sql.Row] = MapPartitionsRDD[6] at map at <console>:33
(5)根据数据及给定的schema创建DataFrame
scala> val dataFrame = spark.createDataFrame(data, structType)
dataFrame: org.apache.spark.sql.DataFrame = [name: string, age: int]
手动构建DataFrame需要的源RDD数据和数据结构
DataFrame转为为RDD
DataFrame的rdd方法,RDD里的泛型为Row
四.DataSet操作
Dataset是具有强类型的数据集合,需要提供对应的类型信息
创建:
1)创建一个样例类
scala> case class Person(name: String, age: Long)
defined class Person
2)创建DataSet
scala> val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]
RDD转换为DataSet
1)创建一个RDD
scala> val peopleRDD = sc.textFile("examples/src/main/resources/people.txt")
peopleRDD: org.apache.spark.rdd.RDD[String] = examples/src/main/resources/people.txt MapPartitionsRDD[3] at textFile at <console>:27
2)创建一个样例类
scala> case class Person(name: String, age: Long)
defined class Person
3)将RDD转化为DataSet
scala> peopleRDD.map(line => {val para = line.split(",");Person(para(0),para(1).trim.toInt)}).toDS()
DataSet转为RDD:
调用rdd方法即可
DataFrame与DataSet互相操作:
1.DataFrame转为DataSet
思路是提供类信息
(1)导入隐式转换
import spark.implicits._
(2)创建样例类
case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
(3)转换
val testDS = testDF.as[Coltest]
2.DataSet转为DataFrame
这个很简单,因为只是把case class封装成Row
(1)导入隐式转换
import spark.implicits._
(2)转换
val testDF = testDS.toDF
RDD,DataFrame,DataSet关系
RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。
在后期的Spark版本中,DataSet会逐步取代RDD和DataFrame成为唯一的API接口。
五.IDEA写Spark SQL
引入Spark SQL依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql_2.11</artifactId> <version>2.1.1</version> </dependency>
六.自定义函数
udf:
SparkSession的udf.register注册自定义的函数,一个函数名一个函数体 。用于遍历每一项时使用
自定义聚合函数:
弱类型用户自定义聚合函数:通过继承UserDefinedAggregateFunction来实现用户自定义聚合函数
import org.apache.spark.sql.expressions.MutableAggregationBuffer import org.apache.spark.sql.expressions.UserDefinedAggregateFunction import org.apache.spark.sql.types._ import org.apache.spark.sql.Row import org.apache.spark.sql.SparkSession object MyAverage extends UserDefinedAggregateFunction { // 聚合函数输入参数的数据类型
def inputSchema: StructType = StructType(StructField("inputColumn", LongType) :: Nil) // 聚合缓冲区中值得数据类型
def bufferSchema: StructType = { StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil) } // 返回值的数据类型
def dataType: DataType = DoubleType // 对于相同的输入是否一直返回相同的输出。 def deterministic: Boolean = true // 初始化
def initialize(buffer: MutableAggregationBuffer): Unit = { // 存工资的总额 buffer(0) = 0L // 存工资的个数 buffer(1) = 0L } // 相同Execute间的数据合并。 def update(buffer: MutableAggregationBuffer, input: Row): Unit =
{ if (!input.isNullAt(0))
{ buffer(0) = buffer.getLong(0) + input.getLong(0)
buffer(1) = buffer.getLong(1) + 1 } } // 不同Execute间的数据合并 def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0) buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1) } // 计算最终结果 def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1) } // 注册函数
spark.udf.register("myAverage", MyAverage) val df = spark.read.json("examples/src/main/resources/employees.json") df.createOrReplaceTempView("employees")
df.show() // +-------+------+ // | name|salary| // +-------+------+ // |Michael| 3000| // | Andy| 4500| // | Justin| 3500| // | Berta| 4000| // +-------+------+ val result = spark.sql("SELECT myAverage(salary) as average_salary FROM employees")
result.show() // +--------------+ // |average_salary| // +--------------+ // | 3750.0| // +--------------+
强类型用户自定义聚合函数:
通过继承Aggregator来实现强类型自定义聚合函数。可以不用取buffer的序列,直接通过字段名获得值
使用的时候先用toColumn.name转为列,DSL方式来用
// Convert the function to a `TypedColumn` and give it a name
val averageSalary = MyAverage.toColumn.name("average_salary") val result = ds.select(averageSalary)
result.show()
七.不同数据源的读取与保存
1.通用加载/保存方法
手动指定选项:
SparkSession提供的read.load方法用于通用加载数据,使用write.save保存数据
format方法指定数据类型
sql方法指定要在文件上运行的sql语句
文件保存选项:
mode,saveMode方法设置
Scala/Java |
Any Language |
Meaning |
SaveMode.ErrorIfExists(default) |
"error"(default) |
如果文件存在,则报错 |
SaveMode.Append |
"append" |
追加 |
SaveMode.Overwrite |
"overwrite" |
覆写 |
SaveMode.Ignore |
"ignore" |
数据存在,则忽略 |
2.Hive
Hive里的语句除了跑SQL,也可以操作数据库和表
Hive分Spark SQL自带的内置Hive和外部Hive
配置:本地使用 --conf spark.sql.warehouse.dir=hdfs://hadoop102/spark-wearhouse 确定内置Hive的位置
注意:如果你使用的是内部的Hive,在Spark2.0之后,spark.sql.warehouse.dir用于指定数据仓库的地址,如果你需要是用HDFS作为路径,那么需要将core-site.xml和hdfs-site.xml 加入到Spark conf目录,否则只会创建master节点上的warehouse目录,查询时会出现文件找不到的问题,这是需要使用HDFS,则需要将metastore删除,重启集群
外部Hive
1) 将Hive中的hive-site.xml拷贝或者软连接到Spark安装目录下的conf目录下。
2) 打开spark shell,注意带上访问Hive元数据库的JDBC客户端
$ bin/spark-shell --jars mysql-connector-java-5.1.27-bin.jar
Saprk SQL CLI
Spark SQL CLI可以很方便的在本地运行Hive元数据服务以及从命令行执行查询任务。在Spark目录下执行如下
代码里使用Hive
1)添加依赖:
<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-hive -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_2.11</artifactId>
<version>2.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hive/hive-exec -->
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>1.2.1</version>
</dependency>
(2)创建SparkSession时需要添加hive支持(红色部分)
val warehouseLocation: String = new File("spark-warehouse").getAbsolutePath
val spark = SparkSession
.builder()
.appName("Spark Hive Example")
.config("spark.sql.warehouse.dir", warehouseLocation)
.enableHiveSupport()
.getOrCreate()
注意:蓝色部分为使用内置Hive需要指定一个Hive仓库地址。若使用的是外部Hive,则需要将hive-site.xml添加到ClassPath下。
第四章.SpardStreaming