快速入门
什么是Spark SQL?
Spark 的一个模块,用于处理 海量 结构化 数据的 分布式SQL计算引擎
为什么学习Spark SQL?
- SparkSQL支持SQL语言、性能极好、可自动优化、API简单、兼容HIVE
- 大面积被使用(离线开发、数仓搭建、数据分析、科学计算)
SparkSQL的特点:
- 融合性
SQL可以无缝集成在代码中,随时用SQL处理数据 - 统一数据访问
一套API可以读写不同数据源 - Hive兼容
可以使用SparkSQL直接计算并生成Hive数据表 - 标准化连接
支持标准化JDBC/ODBC连接,方便和各种数据库交互
Spark SQL 概述
Spark SQL & Hive
Spark SQL 的数据抽象(DataFrame)
Pandas中的数据抽象:DataFrame,二维表数据结构,本地集合
SparkCore的数据抽象:RDD,无标准数据结构,分布式集合(分区)
SparkSQL的数据抽象:DataFrame,二维表数据结构,分布式集合(分区)
借鉴了 Pandas 中的 DataFrame 的底层实现,像是 RDD + 二维表格数据结构限定
除 DataFrame(Python、Java、R、Scala) 外,SparkSQL 的数据抽象还有SchemaRDD(废弃) 和 Dataset(Java、Scala)
Spark Session 对象
RDD 阶段,程序的执行入口对象是 SparkContext
在Spark 2.0 后,推出了 SparkSession对象,作为Spark 编码的统一入口对象
SparkSession对象可以
①用于SparkSQL编程入口对象
②用于SparkCore编程,通过Session获取到Context
获取方法:
from pyspark.sql import SparkSession
if __name__ == '__main__':
spark = SparkSession.builder\ 构建SparkSession执行环境入口对象
.appName("test")\
.master("local[*]")\
.getOrCreate()
sc = spark.sparkContext 通过session获取context对象
DataFrame 入门和操作
DF 的组成
DataFrame 是一个二维表结构,因此绕不开行、列、表结构描述
结构层面:
StructType 对象描述整个DataFrame的表结构
StructField 对象描述一个列的信息 (列名,数据类型,是否可以为空)
StructType = 若干 StructField
数据层面:
Row 对象记录一行数据
Column 对象记录一列数据及列信息(即包含StructField的信息)
DF 的代码构建
-
基于RDD(转换RDD的内部存储结构)
转换后DF中的一行数据,在RDD中需要存储在一个list或元组中,如 [[1, ‘a’],[2, ‘b’],[3, ‘d’]]一、SparkSession对象的createDataFrame() rdd = sc.textFile("../data/input/sql/people.txt").\ map(lambda x: x.split(", ")).\ # 分割,得到一行数据的字符串 map(lambda x: (x[0], int(x[1]))) # 类型转换 1. 通过list传入列描述 参数1:RDD 参数2:列名(list类型) 列类型会自动判断,因此对RDD中的数据做类型转换 DataFrame中的一行数据在RDD中需要封装在一个list中,如[[zjh,18],[gsy,19]] df = spark.createDataFrame(rdd, schema=["name", "age"]) 2. 通过 StructType对象 传入列描述(更详细) schema = StructType()\ .add("name", StringType(), nullable=True)\ .add("age", IntegerType(), nullable=True) df1 = spark.createDataFrame(rdd, schema=schema) 二、RDD.toDF() 方式 df2 = rdd.toDF(schema = ["name", "age"]) 同样包括两种列描述传入方式 df3 = rdd.toDF(schema=schema)
-
基于 Pandas 的 DataFrame(DataFrame的分布式化)
pdf = pd.DataFrame( { "id":[1, 2, 3], "name":["章", "李", "王"], "age":[12, 13, 14] } ) df = spark.createDataFrame(pdf)
-
读取外部数据
通过SparkSQL的统一API进行数据读取构建DataFrame
parquet数据源:spark中常用的一种列式存储文件格式,内置schema(列名、类型、是否为空),序列化存储(有压缩属性体积小)1. Text数据源 # 构建StructType,text数据源,读取数据的特点是, # 将文件中的一行作为`一个列`读取,默认列值类型是String schema = StructType().add("data", StringType(), nullable=True) df = spark.read.format("text").\ schema(schema).\ load("../data/input/sql/people.txt") 2. json数据源 # json文件自带schema信息,可以不写 df1 = spark.read.format("json").load("../data/input/sql/people.json") 3. csv数据源 df2 = spark.read.format("csv").\ option("sep", ";").\ 分隔符 option("header", True).\ 是否有表头 option("encoding", "utf-8").\ 编码 schema("name STRING, age INT, job STRING").\ 一个字符串,串内逗号隔开 load("../data/input/sql/people.csv") 4. parquet数据源 # parquet文件自带schema信息,可以不写 df3 = spark.read.format("parquet").load("../data/input/sql/users.parquet")
DF 入门操作
DataFrame支持两种编程风格:DSL风格 和 SQL风格
-
DSL风格:通过调用API的形式处理Data,如df.where().limit()
DSL称为领域特定语言,就是DataFrame的特有API# TODO DLS风格演示 """ select API,参数为列名或列对象column """ # list参数 df.select(["id", "subject"]).show() # 可变参数 df.select("id", "subject").show() # 获取列对象(也是可变参数) id_column = df['id'] subject_column = df['subject'] df.select(id_column, subject_column).show() """ filter API """ df.filter("score < 99").show() df.filter(df['score'] < 99).show() """ where API """ df.where("score < 99").show() df.where(df['score'] < 99).show() """ groupBy API """ # groupBy的返回值是 GroupData类型的一个对象,该对象不是DataFrame r = df.groupBy("subject") print(type(r)) df.groupBy("subject").count().show() # 分组后一般必带聚合(sum avg count min max等) df.groupBy(df['subject']).count().show()
-
SQL风格:使用SQL语句处理DataFrame的数据,如spark.sql(“select * from xxx”)
DataFrame可以看做一个关系型数据表,使用SQL风格语法只需要将DataFrame注册成表,方式有# TODO SQL风格演示 # 注册一个临时视图(表) df.createTempView("score") # # 注册一个临时表(只能在当前SparkSession中用),若存在则替换他 df.createOrReplaceTempView("score1") # # 注册一个全局表(可跨SparkSession使用,查询前加前缀global_temp.使用) df.createGlobalTempView("score2") spark.sql("select subject, count(*) from score group by subject").show() spark.sql("select subject, count(*) from score1 group by subject").show() spark.sql("select subject, count(*) from global_temp.score2 group by subject").show()
pyspark.sql.functions包
提供一系列的计算函数供SparkSQL使用,这些功能函数的返回值大多是Column对象
导包:from spark.sql import functions as F
拼接表中各列为1列,不同列数据用分隔符隔开
F.concat_ws(“分隔符”, “列名1”, “列名2”, …)
helloworld
# TODO SQL风格
# TODO rdd读取文件,转为df,创建临时表,SQL查询
file_rdd = sc.textFile("../data/input/words.txt").\
flatMap(lambda x: x.split(' ')).\
map(lambda x: [x]) # 一行数据在RDD中需要封装在一个list中
print(file_rdd.collect())
df = file_rdd.toDF(["word"])
# df.show()
df.createTempView("words")
spark.sql("select count(*) as cnt, word from words group by word order by cnt DESC").show()
# TODO DSL风格
# TODO rdd读取文件,转为df,创建临时表,SQL查询
df = spark.read.format("text").load("../data/input/words.txt")
df.show()
# withColumn: 对已存在的列进行操作,返回一个新的列,若和老列名称相同则覆盖,否则追加新列
# 参数:列名,操作函数(返回column)
# 返回值:DataFrame
df1 = df.withColumn("value", F.explode(F.split(df['value'], ' ')))
# 分组 聚合 改列名
df1.groupBy("value").\
count().\
withColumnRenamed("value", 'word').\
withColumnRenamed("count", 'cnt').\
orderBy("cnt", ascending=False).\
show()
电影评分数据分析案例
数据:用户对多部电影的评级数据,包括电影元数据信息和用户属性信息
需求:
- 用户平均分
- 电影平均分
- 大于平均分的电影数量
- 高分电影(>3)中打分次数最多的用户,并求出此人打的平均分
- 每个用户的平均、最高、最低打分
- 被评分超过100次的电影的平均分、排名、Top10
agg:GroupeData对象的API,可以在里面写多个聚合
df.groupBy("mov_id").\
agg(
F.count("mov_id").alias("cnt"), # 统计评分次数
F.round(F.avg("rank"), 2).alias("avg_rank") # 统计每部电影的平均分
)
alias:column对象的API,可以修改某个列的名称
withColumnRenamed:DataFrame的API,可以改df中的列名
first:DataFrame的API,取出df的第一行的row对象(就是一个数组,可以通过row[‘列名’]取到某一列的具体数值)
Spark Shuffle分区数目
运行程序时,通过4040端口监控页面发现,某个stage中有200个Task任务,也就是说RDD有200个分区Partition
原因:在SparkSQL中当Job产生shuffle时,默认的分区数(spark.sql.shuffle.partitions)为200,在实际使用时需要合理设置,否则对local模式下的调度、shuffle传输都会带来压力和损耗
这个参数和RDD中的并行度参数是独立的
设置方式:
代码中:
spark = SparkSession.builder.\
appName("test").\
master("local[*]").\
config("spark.sql.shuffle.partitions", 20).\
getOrCreate()
配置文件:在conf/spark-default.conf中添加spark.sql.shuffle.partitions 100
客户端提交参数中:bin/spark-submit --conf “spark.sql.shuffle.partitions=100”
SparkSQL 数据清洗API
去重
df.dropDuplicates().show() 对数据整体去重
df.dropDuplicates(['age', 'job']).show() 针对特定字段去重,参数必须在list中
缺失值
# 删除
df.dropna().show() # 行中有空即删除该行
df.dropna(thresh=3).show() # 一行至少包括3个有效值,否则删除
df.dropna(thresh=2, subset=['name', 'age']).show() # 一行中的name和age列至少包括2个有效值,否则删除
# 填充
df.fillna("loss").show() # 将参数填充到缺失值区域
df.fillna("loss", subset=['job']) # 指定列进行确实填充
df.fillna({'name':"未知姓名", 'age':18, 'job':'未知职业'}).show() # 通过字典为每一列设置填充值
SparkSQL 数据写出
SparkSQL可以通过统一的API写出DataFrame数据
df.write.mode("overwrite").format("csv").option("sep", "---").save("../data/test.cvs")
mode:写文件模式,append追加,overwrite覆盖,jgnore忽略,error重复就报异常(默认)
format:保存文件的格式,如text,csv,json,parquet,orc,jdbc
opiton:设置属性,如csv文件的分隔符opiton("sep", ",")
save:写出的路径
DataFrame 通过 jdbc 读写数据库
导入 jdbc 驱动 jar包
放到所用python解释器对应的“python环境\Lib\site-packages\pyspark\jars”下
若没有驱动包,spark程序会提示“No suitable Driver”
用 SparkSQL 读写 JDBC数据
将df中的数据写出到mysql中,用format指定数据类型,用option设置数据库、表、用户名、密码
由于写jdbc,因此save中不需要参数指定存储路径
数据库必须提前建好
表会自动创建(因为DataFrame中保存了表的元数据[StructType对象])
df.write.mode("overwrite").\
format("jdbc").\
option("url", "jdbc:mysql://node1:3306/bigdata2?useSSL=false&useUnicode=true").\
option("dbtable", "movie_data").\
option("user", "root").\
option("password", "123456").\
save()
df1 = spark.read.format("jdbc"). \
option("url", "jdbc:mysql://node1:3306/bigdata?useSSL=false&useUnicode=true"). \
option("dbtable", "movie_data"). \
option("user", "root"). \
option("password", "123456").\
load()
总结
Spark SQL 函数自定义
无论hive还是spark,分析处理数据时往往都需要使用函数,SparkSQL自带的公共功能函数(spark.sql.functions)有时无法满足业务需求,这时就需要用户自己定义函数
Hive中有三类自定义函数
UDF:一对一关系,输入一个值,返回一个值
UDAF:聚合函数,多对一关系,输入多个值,返回一个值
UDTF:表生成函数,一对多关系,输入一个值,返回多个值(一行变多行)
SparkSQL 中,目前Python只支持UDF,要实现UDAF,可以通过map partition等算子模拟实现
定义UDF函数
定义方式有2种:
- sparksession.udf.register()
注册的UDF可以用于DSL和SQL风格 - spark.sql.functions.udf
只能用于DSL风格
方式1:spark.udf.register(),可用于DSL和SQL风格
def num_ride_10(num):
return num * 10
# 参数1:udf名称,**该名称仅可以用于SQL风格**
# 参数2:udf的处理逻辑,(函数名)
# 参数3:udf的返回值类型
# 返回值:udf对象,**该对象仅可以用于DSL语法**
udf = spark.udf.register("udf1", num_ride_10, IntegerType())
""" sql风格中使用 """
# selectExpr方法:以select表达式执行,该表达式是SQL风格的表达式(即列名字符串,聚合函数字符串等)
# select方法:接收普通的字符串字段名,或返回值为column对象的计算
df.selectExpr("udf1(num)").show()
""" 风格中使用 """
# 返回的udf对象若作为方法使用,传入的参数必须是column对象
df.select(udf(df['num'])).show()
方式2:spark.sql.udf 仅用于DSL风格
# 参数1:udf的处理逻辑,(函数名)
# 参数2:udf的返回值类型
# 返回值:udf对象,仅可以用于DSL语法
udf2 = F.udf(num_ride_10, IntegerType())
df.select(udf2(df['num'])).show()
定义的UDF的返回值可以多种多样,如Array、Dict
from pyspark.sql.types import StructType, StringType, IntegerType, ArrayType
import pyspark.sql.functions as F
rdd = sc.parallelize([['hadoop flink spark'],['hadoop flink java']])
df = rdd.toDF(['line'])
def split_line(line):
return line.split(" ") # 返回值为一个array对象
# 声明udf返回值为array,array内部元素为String类型
udf = spark.udf.register("udf1", split_line, ArrayType(StringType()))
udf2 = F.udf(split_line, ArrayType(StringType()))
# 假设有3个数字1,2,3,传入数字返回数字所在序号对应的字母,然后和数字结合形成dict返回
# 如传入1,返回{'num':1, 'letters':'a'}
# 构建一个rdd
rdd = sc.parallelize([[1], [2], [3]])
df = rdd.toDF(['num'])
def process(data):
return {"num":data, "letters": string.ascii_letters[data]} # 返回值为一个array对象
# udf返回值为字典,需要用StructType来接收
udf = spark.udf.register("udf1", process, StructType().add('num', IntegerType(), nullable=True).add('letters', StringType(),nullable=True))
df.select(udf(df["num"])).show()
df.createTempView("nums")
spark.sql("select udf1(num) from nums").show(truncate=False) # truncate为FALSE表示全量展示数据
udf2 = F.udf(process, StructType().add('num', IntegerType(), nullable=True).add('letters', StringType(),nullable=True))
df.select(udf2(df["num"])).show(truncate=False)
SparkSQL无法直接定义UDAF,若有需求如何在SparkSQL中实现UDAF?
曲线救国,现将DataFrame转为RDD,此时RDD中的元素为DataFrame中的Row对象,然后通过RDD的mapPartitions算子对RDD中的数据进行聚合,从而实现DataFrame的聚合操作
# 构建一个rdd
rdd = sc.parallelize([1,2,3,4,5], 3).map(lambda x: [x])
df = rdd.toDF(['num'])
# 需求:做一个简易的聚合函数,求所有数字的和
# 折中方式:使用RDD的mapPartitions算子完成聚合,该API要求数据是单分区的,因为它是一个分区操作算子,一次操作一个分区内的数据
# df.rdd:将df转为rdd,df中的一行作为rdd中的一个元素
single_partition_rdd = df.rdd.repartition(1)
print(single_partition_rdd.getNumPartitions())
print(single_partition_rdd.glom().collect())
def process(iter):
sum = 0
for row in iter:
sum += row['num'] # 尽管已经转为了RDD,但单个元素已经不是数字了,而是row对象
return [sum] # mapPartitions要求返回一个可迭代对象
print(single_partition_rdd.mapPartitions(process).collect())
使用窗口函数
窗口函数:窗口 + 函数。
窗口,一组记录的集合,限定了被处理的数据的范围,一个框;
函数:作用在窗口中数据上的逻辑
SQL中的开窗函数:既显示聚集前的数据,又显示聚集后的数据,即在每一行的最后一列添加聚合函数的结果。
如求均值,普通函数只会显示一个均值,若将avg声明为一个窗口函数,则会在结果集中的每一行都追加一列均值
schema = StructType().\
add('name', StringType()).\
add('class', StringType()).\
add('score', IntegerType())
df = rdd.toDF(schema=schema)
df.createTempView('stu')
# TODO 聚合 窗口函数的演示
# avg, min, max, sum, count等标准聚合都可以通过加over()声明为窗口函数使用
spark.sql("""
select *, avg(score) OVER() as avg_score from stu
""").show()
# TODO 排序相关的 窗口函数计算
# rank over, dense_rank over, row_number over
# 全级按分数排名(row_number:连续排名,如1,1,2,3,排名为1,2,3,4)
# 班内按分数排名(dense_rank:并列连续排序,如1,1,2,3,排名为1,1,2,3)
# 全级按分数降序排名(rank:并列跳跃排序,如1,1,2,3,排名为1,1,3,4)
spark.sql("""
select *, row_number() over(order by score desc) as row_number_rank,
dense_rank() over(partition by class order by score desc) as dense_rank,
rank() over(order by score) as rank
from stu
""").show()
# TODO NTILE分组 窗口函数
# ntile:分组函数,将结果集分成n份,每份后追加份的编号
spark.sql("""
select *, NTILE(2) over(partition by class order by score desc) as ntile from stu
""").show()
总结
- SparkSQL支持UDF和UDAF定义,但在Python中,暂时只能定义UDF,要实现UDAF可以将df转为rdd,通过rdd的mapPartitions算子传入自定义聚合逻辑函数曲线救国,实现UDAF
- UDF定义支持两种方式
SparkSession对象(udf名可用于SQL风格,udf对象可用于DSL风格)
udf对象 = spark.udf.register(‘udf名’, udf函数名, udf返回值类型)
functions包中提供的 udf API(只能用于DSL风格)
udf对象 = F.udf(udf函数名, udf返回值类型) - SparkSQL支持窗口函数的使用,常用SQL中的窗口函数都支持,如聚合窗口、排序窗口、NTILE分组窗口等
Spark SQL 运行流程
Spark RDD 的执行流程回顾
RDD代码 -->
DAG调度器生成DAG(逻辑+分区),划分Task(根据分区DAG) -->
Task调度器分配、管理并监控Task运行 -->
Worker干活
SparkSQL 的自动优化
RDD 的运行完全按照开发者的代码执行,其效率受限于开发者水平
SparkSQL 会 自动优化 写完的代码,提升代码运行效率
为什么SparkSQL 可以自动优化,而RDD不可以?
这和他们的存储数据结构有关。
RDD的数据类型不限格式和结构,结构化、非结构化、半结构化都可以,对象、字典、布尔型都可以
DataFrame是一个完全的二维表结构,格式是固定的,不考虑数据,只通过格式信息就可以完成一些优化,这依赖于 catalyst优化器
Catalyst 优化器
为了不过度依赖Hive,SparkSQL 使用了一个新的SQL优化器替代Hive中的优化器,即Catalyst
SparkSQL的执行架构大概如下
API就是DataFrame代码,会通过一些API接收SQL语句
然后将接收到的SQL交给Catalyst,解析SQL,生成RDD执行计划
catalyst的输出应该是RDD的执行计划,即最终转换为RDD去集群中执行
SparkSQL是构建于RDD上的模块,底层依赖RDD
那么,SparkSQL 代码是如何通过 Catalyst 完成优化并转换为 RDD 代码?
catalyst优化器的主要优化步骤有四步
Step1:解析 SQL,生成 AST(抽象语法树,从下往上读)
该SQL有点问题,先join再按age过滤,一般过滤要前置,以提升join的性能
Step2:在 AST 中加入元数据信息,方便 SparkSQL 后续调用这些数据
score.id --> id#1#L 给score.id编号为1,类型是Long
Step3:对已经加入元数据的AST,输入优化器,进行优化
常见的优化有:
断言下推:将判断在语法数中下置,将where、filter等过滤操作前置,这样可以减少操作时的数据量,提高性能
列值裁剪:断言下推后,可以执行裁剪,将用不到的列去除,以减少处理的数据量,优化处理速度。parquet数据存储格式中每个列是单独存储的,因此比较适用于本特性,数据读取时可以直接不读,不需要读后再过滤
SQL的执行顺序一般如下,可以看到select语句执行顺序比较靠后,可能只需要很少几个列,但在前期执行中处理的是全表列的数据,这样就会降低处理速度。
from
on
join
where
group by
having
select
distinct
union
order by
Step4:上面生成的AST是逻辑计划,无法直接运行,需要生成物理执行计划,将计划翻译为RDD代码运行
catalyst优化器总结:
优化点主要有两个:
谓词下推:行过滤,提前执行where。将逻辑判断提前,从而减少shuffle阶段的数据量
列值裁剪:列过滤,提前规划select的字段数量。将加载的列进行裁剪,尽量减少被处理数据的宽度
parquet数据格式很适合列值裁剪,因为它是列式存储,每列单独存储
SparkSQL 的执行流程
- 提交 SparkSQL 代码
- Catalyst 优化
a. 生成原始 AST 语法树
b. 在 AST 中标记元数据
c. 进行断言下推和列值裁剪,及其他方面的优化
d. 得到最终的 AST(逻辑计划),生成执行计划
e. 将执行计划翻译为 RDD 代码- Driver执行环境入口构建(SparkSession)
- DAG调度器规划逻辑任务
- Task调度器分配逻辑任务到具体的Executor上工作,并监控管理任务
- Worker干活
总结
- DataFrame 因为存储的是二维表结构,只基于格式信息就可以完成一些优化,因此可以自动优化
- 自动优化依赖于 Catalyst 优化器
- Catalyst 优化器有两个大的优化项:断言下推(提前行过滤)、列值裁剪(提前列过滤)
- DataFrame 代码在被优化后,最终是被转换为 RDD 去执行
Spark SQL 整合 Hive
原理
回顾 Hive的组件,总的来说就两个:
SQL 优化翻译执行器:翻译SQL到MR,并提交到YARN集群运行
Metastore 元数据管理中心:提供元数据管理服务,让 SQL 知道提取的数据存在 HDFS 中的哪里,是哪一列,是什么类型
SparkSQL 提供将 SQL 翻译成 RDD 的解释执行器,但它缺少 Metastore 元数据信息,因此无法直接读取 HDFS 中的数据
因此,Spark on Hive 简单来说就是:
SparkSQL + MetaStore元数据管理服务
将 Hive 中的 SQL翻译执行引擎 替换成 Spark 的,也可以说是 Spark 提供 SQL执行引擎,借用 Hive 的 MetaStore实现元数据管理功能
Linux 中配置
首先确保Hive中配置了 Metastore 的服务,检查 Hive 配置文件目录中的 hive-site.xml 中有如下配置
基于Spark on Yarn原理,只要让 Spark 连接上 Hive 的 MetaStore 即可
因此:
1、MetaStore 服务需要存在并启动
2、Spark 要知道 MetaStore 在哪里(服务的IP,端口号)
Step1:在 Spark的配置文件路径下 创建 hive-site.xml,告知 hive 的 metastore 在哪里,hive 的数据在 hdfs 中的存储路径
Step2:将 mysql驱动 jar包 放到 Spark 的 jars 目录
因为要连接元数据,会有部分功能连接到 mysql数据库,因此需要驱动
做好上述工作后,在启动 hive 的 metastore 和 hdfs、yarn 的前提下,就可以用 SQL语句,基于 Spark 查看并操作 hive 数据库中的数据了
可以用bin/pyspark (spark.sql(SQL语句))、bin/spark-sql(直接写SQL)交互式的操作 hive数据库
由于通过 Spark 操作 Hive数据库,底层是将 SQL 翻译为 RDD 而非 MR,速度比 hive 快了很多
在代码中集成
配置的内容和Linux集群中的配置相同,只是挪到了代码里,一样可以操作 hive数据库
spark = SparkSession.builder.\
appName("test").\
master("local[*]").\
config("spark.sql.warehouse.dir", "hdfs://node1:8020/user/hive/warehouse").\
config("hive.metastore.uris", "thrift://node1:9083").\
enableHiveSupport().\ 开启hive支持
getOrCreate()
spark.sql("select * from sparkonyarn").show()
结果:
+---+----+
| id|name|
+---+----+
| 1| zjh|
| 2| gsy|
+---+----+
分布式SQL引擎配置
概念
上面章节中通过 Spark 操作 Hive 数据库还是需要一些 Spark 基础,对于只会写 SQL 的人来说有点难度,怎么让他们也通过 Spark 分布式地操作 Hive 数据库?
Hive 是通过 ThriftServer服务(hiveserver2:10000守护进程)作为中介连接第三方数据库工具和 Hive,让人们可以通过 SQL 直接操作 Hive 数据库
Spark 也一样提供一个 ThriftServer守护进程 实现上述功能,连接 第三方数据库工具 和 Spark,守护进程的端口号可以选用10001(因为 10000 通常是 HiveServer2 的端口,避免重复)
我们只需要像使用 HiveServer2 一样启动 ThriftServer 即可
客户端工具连接
和连接HiveServer2一样,写好主机和端口号,测试连接,然后使用
代码 JDBC 连接
在代码中连接Spark的thriftServer需要依赖 pyhive 包,因此需要先在使用的Python环境中安装相应的包
首先安装一系列Linux软件(安装pyhive包依赖他们)
(base) [root@node1 ~]# yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gcc make gcc-c++ python-devel cyrus-sasl-devel cyrus-sasl-plain cyrus-sasl-gssapi -y
安装好前置依赖软件后,安装pyhive包等依赖库。尽管 pyhive 是 Python 连接 hive 的库,但是由于 Spark 的 thriftServer 除了底层是跑 RDD 外,暴露在外的都是按照 HiveServer 那一套做的,所以可以直接通过 pyhive 去连接 Spark 的 thriftServer
(base) [root@node1 ~]# conda activate pyspark
(pyspark) [root@node1 ~]# pip install pyhive pymysql sasl thrift thrift_sasl -i https://pypi.tuna.tsinghua.edu.cn/simple
代码中连接 Spark 的ThriftServer,并读取hive数据库中的数据
from pyhive import hive
if __name__ == '__main__':
""" 用标准的jdbc写法读取hive数据库中的内容 """
""" 底层是用hive还是spark读取决于你Linux集群中启动的thriftServer是谁的 """
# 获取到hive连接(实际上是Spark ThriftServer的连接)
conn = hive.Connection(host='node1', port=10001, username='hadoop')
# 获取游标对象
cursor = conn.cursor()
# 执行SQL
cursor.execute("select * from sparkonyarn")
# 通过fetchall获取返回值
result = cursor.fetchall()
print(result)
总结
分布式SQL执行引擎 就是使用 Spark 提供的 ThriftServer 服务,以"后台守护进程"的模式 持续运行,对外 提供端口
可以通过 客户端 或 代码,以 JDBC协议 连接使用
SQL 提交后,底层运行的就是 Spark 任务
相当于构建了一个以 MetaStore服务 为 元数据,Spark 为 执行引擎 的 数据库服务,像操作数据库那样方便的操作 SparkSQL 进行分布式的 SQL 计算