SparkSQL

快速入门

什么是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 的代码构建

  1. 基于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)
    
    
  2. 基于 Pandas 的 DataFrame(DataFrame的分布式化)

    pdf = pd.DataFrame(
        {
            "id":[1, 2, 3],
            "name":["章", "李", "王"],
            "age":[12, 13, 14]
        }
    )
    df = spark.createDataFrame(pdf)
    
  3. 读取外部数据
    通过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()

电影评分数据分析案例

数据:用户对多部电影的评级数据,包括电影元数据信息和用户属性信息
需求:

  1. 用户平均分
  2. 电影平均分
  3. 大于平均分的电影数量
  4. 高分电影(>3)中打分次数最多的用户,并求出此人打的平均分
  5. 每个用户的平均、最高、最低打分
  6. 被评分超过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()

总结

  1. SparkSQL支持UDF和UDAF定义,但在Python中,暂时只能定义UDF,要实现UDAF可以将df转为rdd,通过rdd的mapPartitions算子传入自定义聚合逻辑函数曲线救国,实现UDAF
  2. UDF定义支持两种方式
    SparkSession对象udf名可用于SQL风格,udf对象可用于DSL风格
    udf对象 = spark.udf.register(‘udf名’, udf函数名, udf返回值类型)
    functions包中提供的 udf API(只能用于DSL风格
    udf对象 = F.udf(udf函数名, udf返回值类型)
  3. 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 的执行流程

在这里插入图片描述

  1. 提交 SparkSQL 代码
  2. Catalyst 优化
    a. 生成原始 AST 语法树
    b. 在 AST 中标记元数据
    c. 进行断言下推和列值裁剪,及其他方面的优化
    d. 得到最终的 AST(逻辑计划),生成执行计划
    e. 将执行计划翻译为 RDD 代码
  3. Driver执行环境入口构建(SparkSession)
  4. DAG调度器规划逻辑任务
  5. Task调度器分配逻辑任务到具体的Executor上工作,并监控管理任务
  6. Worker干活

总结

  1. DataFrame 因为存储的是二维表结构,只基于格式信息就可以完成一些优化,因此可以自动优化
  2. 自动优化依赖于 Catalyst 优化器
  3. Catalyst 优化器有两个大的优化项:断言下推(提前行过滤)、列值裁剪(提前列过滤)
  4. 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 计算

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值