【《Spark SQL 深度探索:内置函数、数据源处理与自定义函数,SparkSQL连接Hive实践》】

前言:
💞💞大家好,我是书生♡,本阶段和大家一起分享和探索大数据技术Spark—SparkSQL,本篇文章主要讲述了:Spark SQL 深度探索:内置函数、数据源处理与自定义函数实践等等。欢迎大家一起探索讨论!!!
💞💞代码是你的魔法,创造是你的舞台,用它们编织梦想,跨越障碍,探索无限的可能!!

个人主页⭐: 书生♡
gitee主页🙋‍♂:闲客
专栏主页💞:大数据开发
博客领域💥:大数据开发,java编程,前端,算法,Python
写作风格💞:超前知识点,干货,思路讲解,通俗易懂
支持博主💖:关注⭐,点赞、收藏⭐、留言💬

1. SparkSQL内置函数

1.1 窗口函数

  Apache Spark SQL 提供了一组强大的窗口函数,允许用户执行复杂的分析操作而无需复杂的子查询或者自定义函数。窗口函数可以在每个分区内对数据进行排序,并计算各种聚合值,如累计和、移动平均等。

Spark SQL 支持的窗口函数包括但不限于:

  • row_number()

为每一行分配一个唯一的、连续的整数。

  • rank()

分配一个唯一的排名给每一行,跳过被超越的排名值。

  • dense_rank()

分配一个唯一的排名给每一行,不跳过被超越的排名值。

  • percent_rank()

计算每一行相对于分区中其他行的百分比排名。

  • cume_dist()

计算每一行相对于分区中其他行的累积分布。

  • lead(expression, [offset], [default])

返回给定表达式在当前行之后第 offset 行的值(默认 offset 为 1)。

  • lag(expression, [offset], [default])

返回给定表达式在当前行之前第 offset 行的值(默认 offset 为 1

  • sum(expression),avg(expression),min(expression),max(expression)

对表达式计算聚合值,如总和、平均值、最小值或最大值。

  • count(expression)

计算非空值的数量。

窗口函数语法:
排序函数--排序的序号 F.rank() F.row_number() F.dense_rank()

第一步:创建df对象

# 使用sparkcontext读取hdfs上的文件数据
# 将读取的文件数据转化为rdd
def getStudentDataFrame(sc,path,schema):
    rdd = sc.textFile(path)
    res1 = rdd.collect()
    rdd_split = rdd.map(lambda x: x.split(','))
    res2 = rdd_split.collect()
    df = rdd_split.toDF(schema=schema)
    df_select = df.select('*').limit(10)
    return df_select
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
from studentdataframe import getStudentDataFrame

# 创建sparksession对象
spark = SparkSession.builder.getOrCreate()
# 创建sc对象
sc = spark.sparkContext
# 创建df对象
file_path = 'hdfs://node1:8020/data/stu.txt'
schema = ('id string,name string,gender string,age string,'
          'birthday string,major string,hobby string,create_time string')
df = getStudentDataFrame(sc, file_path, schema)
df.show()

第二步:在 select 方法中直接应用窗口函数

df_select=df.select(df.id.cast('int').alias('id'),df.name,df.gender,df.age.cast('int').alias('age'),df.birthday,df.major,df.hobby,df.create_time,)
df_select.printSchema()

df_row=df_select.select('id','name','gender','age',F.row_number().over(Window.partitionBy('gender').orderBy(df_select.age.desc())).alias('rn'))
df_row.select('id','name','gender','rn','age').where('rn<=3').show()

df_row1 = df_select.select(
    "id",
    "name",
    "gender",
    "age",
    F.max("age").over(Window.partitionBy("gender")).alias("max_age")
)
df_row1.show()

1.2 when&otherwise 函数

在 PySpark 中,when 和 otherwise 是非常常用的函数,用于创建条件表达式,特别是在 DataFrame API 中。这些函数通常与 case 一起使用,允许您根据条件对数据进行转换。

F.when(condition=,value).when().otherwise(value)

使用 when 和 otherwise 的基本语法

from pyspark.sql import functions as F
# 创建一个基于条件的列
df = df.withColumn('new_column', F.when(condition, value_if_true).otherwise(value_if_false))

示例:根据年龄划分成 青年: 0<=age<30 中年: 30<=age<60 老年: age>=60, 统计不同年龄段学生人数

df_select = df.select('id','name','gender','age',
                     F.when((F.col('age') >=0) & (F.col('age') < 22) ,'青年').
                     when((F.col('age') >=22) & (F.col('age') <60) ,'中年').
                     otherwise('老年').alias('age_stage'))
df_select.show()
df_select.groupBy('age_stage').agg(F.count('id').alias('cnt')).show()

2. SparkSQL读写数据

在 Apache Spark 中,Spark SQL 提供了一种简单的方式来读取和写入各种数据源。下面是一些常见的数据源以及如何使用 Spark SQL 读写这些数据源的方法。

读取数据

  1. CSV 文件
df = spark.read.format("csv") \
               .option("header", "true") \
               .option("inferSchema", "true") \
               .load("path/to/csv/file.csv")
  1. JSON 文件
df = spark.read.json("path/to/json/file.json")
  1. Parquet 文件
df = spark.read.parquet("path/to/parquet/file.parquet")
  1. JDBC 数据源
properties = {"user": "username", "password": "password"}
df = spark.read.jdbc(url="jdbc:mysql://localhost:3306/database", 
                     table="table_name", 
                     properties=properties)
  1. Avro 文件
df = spark.read.format("avro").load("path/to/avro/file.avro")

写入数据

  1. CSV 文件
df.write.format("csv") \
       .option("header", "true") \
       .mode("overwrite") \
       .save("path/to/output/csv")
  1. JSON 文件
df.write.json("path/to/output/json")
  1. Parquet 文件
df.write.parquet("path/to/output/parquet")
  1. JDBC 数据源
properties = {"user": "username", "password": "password"}
df.write.jdbc(url="jdbc:mysql://localhost:3306/database", 
              table="table_name", 
              mode="overwrite", 
              properties=properties)
  1. Avro 文件
df.write.format("avro").save("path/to/output/avro")

其他注意事项

  • 数据源格式:Spark SQL 支持多种数据源格式,包括 CSV、JSON、Parquet、Avro、ORC 等。
  • 写入模式mode 参数可以是 "append", "overwrite", "ignore""error"
  • 分区:对于较大的数据集,可以使用分区来优化写入和读取性能。
    df.write.partitionBy("year", "month").parquet("path/to/partitioned/parquet")
    

2.1 从文件读取数据

格式:
df = spark.read.format("格式") .load("path/to/csv/file.csv")

2.1.1 txt文件读取

print("======================== txt文件读取=====================")
# 第一种方法
df_text = spark.read.text('hdfs://node1:8020/data/stu.txt')
df_text.show()

df_select =df_text.select(F.split(df_text.value,",")[0].cast("int").alias("id"),
                          F.split(df_text.value,",")[1].alias("name"),
                          F.split(df_text.value,",")[2].alias("gender"),
                          F.split(df_text.value,",")[3].cast("int").alias("age"),
                          F.split(df_text.value,",")[4].alias("birthday"),
                          F.split(df_text.value,",")[5].alias("major"),
                          F.split(df_text.value,",")[6].alias("hobby")
                          )
df_select.show()

# 第二种方法
spark.read.format('text').load(path="hdfs://node1:8020/data/stu.txt").show()

在这里插入图片描述
在这里插入图片描述

2.1.2 CVS文件读取

csv文件需要指定分隔符,默认是逗号
csv也是一个文本文件格式, 可以指定分隔符(默认为逗号,)
header=True: 第一行为列名
inferSchema=True: 自动推断数据类型
schema: 设置表结构 列名和类型

print("========================csv文件读取=====================")
df_csv = spark.read.csv('hdfs://node1:8020/data/stu.csv',sep=',',
                         schema='id string,name string,gender string,age string,birthday string,major string,hobby string,create_time string')
df_csv.show()

df_csv1 = spark.read.format('csv').load(path="hdfs://node1:8020/data/stu.csv",sep=',',
                         schema='id string,name string,gender string,age string,birthday string,major string,hobby string,create_time string')
df_csv1.show()

在这里插入图片描述

2.1.3 json文件读取

json文件可以不指定schema,会根据字典自动解析

print("========================json文件读取=====================")

df_json = spark.read.json('file:///export/server/spark/examples/src/main/resources/employees.json')
df_json.show()

在这里插入图片描述

2.1.4 其他文件

print('-------------------orc文件读取---------------------')
df_orc = spark.read.orc('file:///export/server/spark/examples/src/main/resources/users.orc')
df_orc.show()
print('-------------------parquet文件读取---------------------')
df_parquet = spark.read.parquet('file:///export/server/spark/examples/src/main/resources/users.parquet')
df_parquet.show()

在这里插入图片描述

2.2 往文件写入数据

数据写入一共有三种方式:
df.write.text/json/csv/jdbc...(path=, mode=) df.write.save(format='text/json/csv/jdbc', path=, mode=) df.write.format().mode().save(path=)mode参数写入方式: ①append: 追加写入 ②overwrite: 覆盖写入

  • txt格式文件
    • df.write.text(path=)
    • df.write.save(format='text', path=, mode=)
    • df.write.format().mode().save(path=)
  • json格式文件
    • df.write.json(path=, mode=)
    • df.write.format().mode().save(path=)
  • csv格式文件
    • df.write.csv(path=, sep=, mode=)
    • df.write.format().mode().save(path=)
  • orc格式文件
    • df.write.orc(path=, mode=)
    • df.write.format().mode().save(path=)
  • parquet格式文件
    • df.write.parquet(path=, mode=)
    • df.write.format().mode().save(path=)
  • mysql数据
    • df.write.jdbc(url=, table=, mode=,properties=)
    • df.write.format().mode().save(path=)

准备数据:

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

# 创建sparksession对象
spark = SparkSession.builder.getOrCreate()
# 创建df对象
df = spark.createDataFrame(data=[[1, '小明', 18, '男'],
								 [2, '小红', 20, '女'],
								 [3, '张三', 22, '女'],
								 [4, '李四', 24, '男']],
						   schema='id int,name string,age int,gender string')
df.show()

2.2.1 text文件写入

text()方法中是没有mode参数, 只支持一次性写入, 不能重复执行,因此不建议使用,建议使用

  • df.write.save(format='text', path=, mode=)
  • df.write.format().mode().save(path=)
print('--------------------写入text文件------------------')
# todo df.write.text/json/csv/jdbc...(path=, mode=)
# text()方法中是不支持mode参数,写mode参数运行会报错, 只支持一次性写入, 不能重复执行
# df.write.text(path='/data/student.txt', mode='append')
# todo df.write.save(format='text/json/csv/jdbc', path=, mode=)
df_text.write.save(format='text',path='/data/student.txt', mode='overwrite')
df_text.write.save(format='text',path='/data/student.txt', mode='append')
# todo df.write.format().mode().save(path=)
df_text.write.format('text').mode('append').save(path='/data/student.txt')

在这里插入图片描述

2.2.2 text文件写入

# todo df.write.text/json/csv/jdbc...(path=, mode=)
df.write.csv(path='/data/student.csv',sep=',', mode='overwrite')
# todo df.write.format().mode().save(path=)
df.write.save(format='csv',path='/data/student.csv',sep=',', mode='overwrite')

在这里插入图片描述

2.2.3 json文件写入

print('--------------------写入json文件------------------')
# todo df.write.format().mode().save(path=)
df.write.format('json').mode('overwrite').save(path='/data/student.json')

在这里插入图片描述

2.3 数据库的读取写入

2.3.1 数据库读取数据

spark.read.jdbc(url='mysql的url', table='表名', properties=配置信息 账号,密码,驱动类型)

print("========================数据库读取=====================")
# url: mysql的url
# table: 表名
# properties: 配置信息  账号,密码,驱动类型
df_mysql = spark.read.jdbc(url='jdbc:mysql://192.168.88.100:3306/spark_test?characterEncoding=UTF-8',
                           table='test',
                           properties={'user':'root',
                                       'password':'123456',
                                 'driver':'com.mysql.jdbc.Driver'})
df_mysql.show()

在这里插入图片描述

2.3.2 写入数据库

spark.read.jdbc(url='mysql的url',table='表名', mode='写入方式', properties=配置信息 账号,密码,驱动类型)

print('-------------------------------数据表写入--------------------------')
# todo 如果表不存在会自动创建, 也可以先创建表再写入(表字段类型要和df列值类型一致)
df.write.jdbc(url='jdbc:mysql://192.168.88.100:3306/spark_test?characterEncoding=UTF-8',
              table='stu',
              mode='overwrite',
              properties={'user': 'root',
                          'password': '123456',
                          'driver': 'com.mysql.jdbc.Driver'
              })

在这里插入图片描述

3. 自定义函数

3.1 函数分类

  • UDF(User-Defined-Function)函数
    • 一对一关系
    • 原df一行数据经过UDF函数处理, 返回新df的一行结果(函数的返回值)
    • concat/concat_ws/split/substring…
    • 可以自定义
  • UDAF(User-Defined Aggregation Function)函数
    • 多对一关系
    • 原df多行数据经过UDAF函数处理, 返回新df的一行结果(函数的返回值)
    • max/min/sum/mean/count…
    • 可以自定义 -> 借助Pandas模块中的Series对象
  • UDTF(User-Defined Table-Generating Function)函数
    • 一对多关系
    • 原df一行数据经过UDTF函数处理, 返回新df的多行结果(函数的返回值)
    • explode -> 爆炸/炸裂函数

3.2 UDTF—explode函数

explode(col)函数:
接收array类型的列对象 -> array类型等同于python中的list类型
将[值1, 值2, …]结构的数据炸裂开, 列表中有多少个元素返回多少行数据

案例:将文件中的数据读取出来,统计每一个单词的个数

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = SparkSession.builder.getOrCreate()

df_text = spark.read.text('/data/words.txt')
df_text.show(truncate=False)
df_split = df_text.select(F.split('value',',').alias('value'))
df_split.show(truncate=False)


df_explode = df_split.select(F.explode('value').alias('word'))
df_explode.show(truncate=False)


df_group = df_explode.groupBy('word').agg(F.count('word').alias('cnt'))
df_group.show(truncate=False)

# todo 通过sparksql实现
df_text.createOrReplaceTempView('text')
spark.sql(sqlQuery="""
select word,count(word) cnt 
from (select explode(split(value ,',')) as word
from text) temp group by word
order by cnt DESC
""").show()

3.3 自定义UDF函数

创建自定义的函数需要将函数注册到Spark中:

  • 普通注册方式 ------普通注册方式在DSL和SQL方式中都能使用
  • 装饰器注册方式-------装饰器注册方式只能在DSL方式中使用

3.3.1 普通注册方式

自定义UDF函数使用步骤:以DSL和SQL语句两种方式种使用

  • 创建自定义函数 -> python函数, 函数的参数是某列中的一行数据
  • 将自定义函数注册spark中
    • 普通注册方式 -> 借助sparksession对象
      变量名 = sparksession.udf.register(name=新函数名, f=自定义UDF函数名, returnType=函数的返回值类型)

name:新函数名, 一般和变量名重名
f:自定义UDF函数名
returnType: 说明自定义UDF函数的返回值,
如果返回值是嵌套类型, 需要外层和内层都要说明, spark的类型对如果返回值是字符串类型, 可以省略不写, 默认类型

注意:注册的过程中有两个函数名,一个是我们自定义的另一个是我们在上面自定义的函数名,DSL使用的是我们后定义的,sql使用的我们参数里面的函数名

在这里插入图片描述

from pyspark.sql import SparkSession
from pyspark.sql.types import ArrayType, StringType
import re

spark = SparkSession.builder.getOrCreate()

def info(email):
    # 正则表达式
    # ()分组 .*?->非贪婪模式匹配
    res =r'(.*?)@(.*?)[.](.*)'
    fa = re.match(res,email)
    return fa.group(1),fa.group(2)

df = spark.read.load(format='csv', path='/data/stu.csv',
                     sep=',',
                     schema='name string,age int,'
                            'gender string,phone string,email '
                            'string,city string,address string')

# 将自定义UDF函数注册到spark中, 类似spark的内置函数, 可以对df的列进行处理
# 普通注册方式

email_func = spark.udf.register(name="email_func",f=info,returnType=ArrayType(StringType()))
df.select('email',
          email_func('email')[0].alias('user'),
          email_func('email')[1].alias('company')).show()

print("------------------------------------------")
df.createTempView('email')
spark.sql(sqlQuery="""
select email,email_func(email)[0] as user,
email_func(email)[1] as company from email
""").show()

3.3.2 装饰器注册方式

@udf(returnType=): def 函数名(): ... 只能在DSL方式中使用

我们只需要在定义函数的是,在函数名上面加上一个注解
注意:返回值为字符串的时候,不需要写returnType,如果是其他的形式需要指定返回的格式

使用的是只要在select方法中,调用函数传入参数就可以了

from pyspark.sql.functions import udf
from pyspark.sql import SparkSession
from pyspark.sql.types import ArrayType, StringType
import re

spark = SparkSession.builder.getOrCreate()

print('**************************************************')
@udf(returnType=StringType())
def to_age(age):
    if age>=0 and age<30:
        return '青年'
    elif age>=30 and age<60:
        return '中年'
    elif age>=60:
        return '老年'

df_csv = spark.read.load(format='csv', path='/data/stu.csv',
                     sep=',',
                     schema='name string,age int,'
                            'gender string,phone string,email '
                            'string,city string,address string')

'''
 @udf(returnType=)
 def 函数名():
     ...
 # 只能在DSL方式中使用
'''

# todo 装饰器注册方式
df_age=df_csv.select('name','age',to_age('age').alias('age_stage'))
df_age.show()
df_age.groupBy('age_stage').count().show()


3.4 Pandas的DF和Spark中的DF相互转换

在udf函数中,对字段数据的处理是一行一行的处理,无法对整个字段中的所有数据一次性处理,也就是无法完成聚合的操作
求和,求平均数,最大值,最小值等都需要获取整个字段的数据内容,udf函数中无法实现,此时就需要使用udaf函数
在这里插入图片描述

UDAF函数需要借助pandas中series类型数据,该类型的数据可以接受字段的一整列数据,完成对字段所有数据的聚合操作

  • Pandas
 Pandas是python的一个数据分析包(numpy,matlab),最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发team继续开发和维护,属于PyData项目的一部分。

pandas是单机资源计算,不适合大数据计算场景

Pandas最初被作为金融数据分析工具而开发出来,因此pandas为时间序列分析提供了很好的支持。 

Pandas的主要数据结构是 Series (一维数据)与 DataFrame(二维数据),这两种数据结构足以处理金融、统计、社会科学、工程等领域里的大多数典型用例。

通过索引取值  行索引  列索引

Series 代表一列或一行数据  只有行索引  取出某行或某列数据

DataFrame 有行和列  同时有行索引、列索引,通过行列索引取出的值就转为series类型
spark的dataframe  row对象  row[0] 和 schema信息(列的名字)  
  • series

是一种类似于一维数组的对象,它由一组数据以及一组与之相关的数据标签(即索引)组成。

  • DataFrame

是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值),可以看作是一种二维表格型的数据结构,既有行索引也有列索引,行索引是 index,列索引是 columns。它可以被看做由 Series 组成的字典(共同用一个索引)。

  • pandas的dataframe和spark的dataframe的转化

pandas的数据在计算时使用的是单机资源进行计算,想要进行分布式计算,利用多台计算机资源,此时就可以将pandas的dataframe转化为spark的dataframe

# 导入SparkSession模块,用于创建Spark会话
# 导入pandas库,用于数据处理
from pyspark.sql import SparkSession
import pandas as pd

# 创建或获取现有的Spark会话
spark = SparkSession.builder.getOrCreate()

# 使用pandas创建一个DataFrame,包含姓名和年龄数据
pd_df = pd.DataFrame(data={'name': ['zhjangsan', 'lisi', 'wangwu'],
                           'age': [0, 20, 30]})
# 打印pandas DataFrame以查看数据
print(pd_df)

# 将pandas DataFrame转换为Spark DataFrame,并指定schema
spark_df = spark.createDataFrame(pd_df, schema=['name', 'age'])
# 打印Spark DataFrame以查看数据
print(spark_df.show())

# 将Spark DataFrame转换回pandas DataFrame
s_to_p = spark_df.toPandas()
# 打印转换后的pandas DataFrame以查看数据
print(s_to_p)

3.5 自定义UDAF函数

udaf函数:多进一出

借助pandas模块中的Series和dataframe对象 pip install pandas

在使用自定义udaf函数之前需要在python解析器中安装pyspark[sql] pip install pyspark[sql]

  • py4j模块
    • 将python代码转化成java代码, 交给JVM进程执行
  • pyarrow模块
    • 是一种内存中的列式数据格式
    • 提高pandas的series对象的传输速度
 # 指定使用arrow采用列式格式提升传输速度
 ss = SparkSession.builder.master('yarn').config('spark.sql.execution.arrow.pyspark.enabled','true').getOrCreate()

udaf函数使用步骤:

  • 定义udaf函数
    • 需要说明函数参数类型 -> pandas的series类型(pd.Series)或dataframe类型(pd.DataFrame)
    • 需要说明函数返回值类型 -> python类型或pandas的series类型(pd.Series)或dataframe类型(pd.DataFrame)
  • 将udaf函数通过装饰器方式注册到spark中
@pandas_udf(returnType=StringType())
def 函数名(参数名:参数类型)->返回值类型:
   pass
  • 使用udaf函数处理df的列数据
    • 只能在DSL方式中使用
    • 可以将udaf函数进一步注册成spark的udf函数, 就可以在SQL语句方式中使用
  • 要指明函数参数的类型, pandas的pd.Series类型或pd.DataFrame类型 参数名: 类型
  • 要指定函数返回值的类型 -> python类型或pd.Series类型或pd.DataFrame类型
# user define aggregate function
# UDAF函数
from pyspark.sql import SparkSession, functions as F
from pyspark.sql.types import *
import pandas as pd
from pyspark.sql.functions import pandas_udf

spark = SparkSession.builder.getOrCreate()

# 读取学生数据
df_csv = spark.read.csv('hdfs://node1:8020/data/stu.csv', sep=',',
                     schema='name string,age int,gender string,'
                            'phone string,email string,city string,'
                            'address string'
                     )
@pandas_udf(returnType=FloatType())
def age_func(age:pd.Series)->float:
    return age.mean()

df_csv.select(age_func('age')).show()


df_csv.groupby('gender').agg(age_func('age')).show()

注意:UDAF只能用于DSL方式,如果要通过sql 注册的话还需要再次将可以将udaf函数进一步注册成spark的udf函数, 就可以在SQL语句方式中使用

# user define aggregate function
# UDAF函数
from pyspark.sql import SparkSession, functions as F
from pyspark.sql.types import *
import pandas as pd
from pyspark.sql.functions import pandas_udf

spark = SparkSession.builder.getOrCreate()

# 读取学生数据
df_csv = spark.read.csv('hdfs://node1:8020/data/stu.csv', sep=',',
                     schema='name string,age int,gender string,'
                            'phone string,email string,city string,'
                            'address string'
                     )
@pandas_udf(returnType=FloatType())
def age_func(age:pd.Series)->float:
    return age.mean()
    
df_csv.createTempView('age_info')
avg_func= spark.udf.register(name='age_func',f=age_func)
spark.sql("""
    select gender,age_func(age) as avg_age from age_info group by gender
""").show()

4. sparksession对象操作

创建sparksession对象时可以通过master()/appName/config()方法选择部署模式/设置程序名称/设置其他配置参数

master/appName方法底层调用的就是config方法

config(key=,value=,conf=)

  • key:参数名, 字符串类型
  • value:参数值, 字符串类型
  • conf:SparkConf对象 SparkConf().set(key, value).set()…

spark.sql.shuffle.partitions: 调整SparkSQL shuffle过程中的分区数
SparkSQL在进行shuffle时默认分区数为200, 可以通过此参数调整默认分区数(合理分配集群资源)

在builder和getOrCreate()之间调用其他方法

  • master(): 设置spark的部署模式
  • appName(): 设置spark应用程序名称
  • config(): 设置spark的其他配置参数
from pyspark.sql import SparkSession

# 创建sparksession对象
# 在builder和getOrCreate()之间调用其他方法
# master(): 设置spark的部署模式
# appName(): 设置spark应用程序名称
# config(): 设置spark的其他配置参数
"""
spark.sql.shuffle.partitions: 调整SparkSQL shuffle过程中的分区数
SparkSQL在进行shuffle默认分区数为200, 可以通过此参数调整默认分区数
"""

# 创建sparksession对象
# 在builder和getOrCreate()之间调用其他方法
# master(): 设置spark的部署模式
# appName(): 设置spark应用程序名称
# config(): 设置spark的其他配置参数

spark = (SparkSession.builder.appName("test").
         master("yarn").
         config("spark.sql.shuffle.partitions", 4).
         getOrCreate())

5. SparkSQL&Hive

spark on hive模式:

  • spark工具进行数据处理, 计算引擎
  • hive的metastore服务进行元数据管理

Spark on Hive 模式是指 Apache Spark 使用 Apache Hive 的元数据服务和表格式来处理数据的一种方式。这种方式允许 Spark 直接读取和写入 Hive 表中的数据,并利用 Hive 的功能,如分区、桶排序等。

  1. 元数据管理
  • Hive Metastore:Spark on Hive 利用 Hive Metastore 来存储和管理所有表的元数据信息。这意味着 Spark 可以查询 Hive Metastore 来获取表结构、分区信息等。
  • 兼容性:由于使用了相同的元数据服务,Spark 和 Hive 之间可以共享相同的表定义,无需额外的数据迁移或转换。
  1. 数据存储
  • Hive 表格式:Spark on Hive 支持 Hive 所支持的所有文件格式,包括但不限于 TextFile、ORC、Parquet、Avro 等。
  • 数据位置:数据通常存储在 HDFS 或其他支持的分布式文件系统上,Spark 能够直接读取这些文件系统中的数据。
  1. 查询优化
  • Hive 规则:Spark 使用 Hive 的规则来进行查询优化,例如自动应用分区过滤、桶排序等。
  • 执行引擎:尽管使用了 Hive 的元数据和表格式,但 Spark 使用自己的执行引擎(如 Spark SQL)来执行查询,这通常比 Hive 的 MapReduce 执行引擎更快。
  1. 动态资源分配
  • YARN:在集群环境中,Spark on Hive 可以通过 YARN 或其他资源管理器动态地分配资源。
  1. 集成与互操作性
  • 无缝集成:Spark on Hive 提供了一种无缝集成 Spark 和 Hive 的方式,使得开发人员可以在两种系统之间轻松切换,而不需要关心底层的技术细节。
  • 工具支持:许多大数据工具,如 Apache Zeppelin 或 Jupyter Notebook,都支持 Spark on Hive 的配置,方便用户进行数据分析和报告生成。
  1. 管理和监控
  • 日志和度量:Spark on Hive 的作业可以被监控和管理,可以通过 Spark UI 或其他监控工具查看作业状态、性能指标等。

5.1 SparkSQL启动配置

在使用SparkSQL之前需要配置hive的metastore服务

# 将hive中的hive-site.xml配置文件复制到spark/conf目录下
cp /export/server/hive/conf/hive-site.xml /export/server/spark/conf/

在这里插入图片描述

5.2 SparkSQL使用

因为我们的元数据是由metastore管理的,因此要先启动hive的metastore服务。

hive --service metastore &

5.2.1 SparkSQL的交互式开发

我们的sparksql的交互式开发和pyspark的差不多

  • spark-sql:启动SparkSQL 可以设置指令参数
  • spark.sql.warehouse.dir:设置SparkSQL的映射路径,
    如果不设置默认存在本地磁盘 /root/sparkwarehouse/itcast.db
spark-sql --master yarn --conf spark.sql.warehouse.dir=hdfs://node1:8020/user/hive/warehouse

5.2.2 脚本开发

  • 创建sparksession对象的时候需要指定spark数据存储位置
  • enableHiveSupport(): 集成hive
  • 将df对象数据写入到hive表中,format=‘hive’ ,format: 使用hive格式, 表存在时需要指定, 如果表不存在, 可以不需要指定(如果表在hive中, 需要指定format=‘hive’)
from pyspark.sql import SparkSession
# 创建SparkSession对象
# spark.sql.warehouse.dir 指定数仓路径   表中的每行数据可以从hdfs上获取
spark = (SparkSession.builder.
         master("local[*]").
         config('spark.sql.warehouse.dir', 'hdfs://192.168.88.100:8020/user/hive/warehouse').
         enableHiveSupport().
         appName("test").
         getOrCreate())

# 建表语句
spark.sql("create table if not exists itcast.spark_user(id int,name string,age int,gender string)")

# 插入数据
spark.sql("insert into itcast.spark_user values(1,'张三',20,'男')")

# 创建df对象
df = spark.createDataFrame(
   data = [
        (1,'张三',20,'男'),
        (2,'李四',21,'女'),
        (3,'王五',22,'男'),
        (4,'赵六',23,'女'),
        (5,'田七',24,'男')

    ],
   schema = ['id','name','age','gender']
)

# 写入hive
# format='hive' ,format: 使用hive格式, 表存在时需要指定, 如果表不存在, 可以不需要指定
# 如果表在hive中, 需要指定format='hive'
df.write.saveAsTable(name='itcast.spark_user',
                     mode='overwrite',format='hive')
df.write.saveAsTable(name='itcast.spark_user1',
                     mode='append')

# 读取数据
df = spark.sql("select *from itcast.spark_user1")
# 显示数据
df.show()

5.3 Pycharm/DataDrip工具连接SparkSQL

要先启动启动spark的二代服务

#启动spark的二代服务
/export/server/spark/sbin/start-thriftserver.sh --hiveconf hive.server2.thrift.port=10001 --hiveconf hive.server2.thrift.bind.host=node1  --conf spark.sql.warehouse.dir=hdfs://node1:8020/user/hive/warehouse --master yarn
# 关闭spark的二代服务
stop-thriftserver.sh

在这里插入图片描述
在这里插入图片描述

5.4 SparkSQL的catalyst引擎

Spark SQL 的 Catalyst 优化器是 Spark SQL 中的核心组件之一,负责查询计划的生成和优化。Catalyst 引擎的设计目的是为了提高查询性能和减少内存使用,同时保持高度的灵活性和可扩展性。以下是关于 Catalyst 引擎的一些关键概念和技术细节:

Catalyst 引擎概述

  • 模块化架构:Catalyst 采用模块化设计,分为多个阶段,每个阶段都有特定的任务。
  • 规则驱动:优化规则是 Catalyst 的核心,这些规则定义了如何转换逻辑计划和物理计划。
  • 可扩展性:Catalyst 的设计允许轻松添加新的优化规则,以便支持新的数据源或查询类型。
  • 作用: 将SparkSQL代码转化成RDD代码, 交给spark工具执行(SparkSQL底层提交的还是RDD任务)
  • catalyst是由解析器、分析器、优化器和执行器组成
  • 优化器-> 四步骤最重要的步骤, 可以实现对SparkSQL代码的优化操作

Catalyst 的主要组成部分

  • 解析器 (Parser):将 SQL 语句转换成抽象语法树 (AST)。
  • 分析器 (Analyzer):验证 AST 的正确性,将其转换为逻辑计划 (Logical Plan)。
  • 优化器 (Optimizer):对逻辑计划进行优化,生成优化后的逻辑计划。
  • 计划生成器 (Physical Planner):从优化后的逻辑计划生成最优的物理计划 (Physical Plan)。
  • 代码生成器 (Code Generator):使用 Spark 的 Tungsten 项目生成高效的 JVM 字节码。

Catalyst 的工作流程

  1. 解析 (Parsing):将 SQL 查询转换为抽象语法树。,将SQL语句转化成未解析的逻辑查询计划
  2. 分析 (Analysis):验证查询的有效性,并将其转换为逻辑计划。
  3. 优化 (Optimization)
    • 逻辑优化:对逻辑计划进行优化,例如常量折叠、谓词下推等。
    • 物理优化:选择最佳的执行策略和算子,如选择最佳的连接算法。
  4. 物理计划生成:根据优化后的逻辑计划生成物理执行计划。
  5. 代码生成:生成高效的执行代码。

Catalyst 的特点

  • 规则驱动:Catalyst 通过一系列规则来驱动优化过程,这些规则可以是内置的也可以是自定义的。
  • 多阶段优化:Catalyst 将优化过程分解为多个阶段,每个阶段都有特定的优化目标。
  • 成本模型:Catalyst 使用成本模型来评估不同物理计划的成本,从而选择最高效的执行计划。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值