spark

spark数据分析导论

spark是什么?

spark是一个用来实现快速而通用的集群计算平台。主要的特点就是在内存中进行计算。

spark的软件栈

在这里插入图片描述

spark Core:实现了spark的基本功能,包括任务调度、内存管理、错误恢复与存储系统交互等模块以及对RDD(弹性分布式数据集)的定义及相关的API操作。

spark Sql:是spark用来操作结构化数据的程序包,支持Apache Hive 版本的HQL来进行数据查询。

**spark streaming:**是spark提供的对实时数据进行流式计算的组件。

**MLlib:**提供了常见的机器学习功能的程序库,提供了一些算法的实现:分类、回归、据类以及模型评估等。

**GraphX:**提供了操作图的程序库,可以进行并行的图计算。

RDD编程

RDD概述

**RDD:**弹性分布式数据集,其实就是分布式的元素集合。spark自动将RDD的数据分发到集群上,并将操作并行化执行。

RDD操作分为转化操作和行动操作,转化操作会由一个RDD生成一个新的RDD,行动操作会对RDD计算出一个结果,并把结果返回到驱动器程序中。转化操作是惰性的,需要行动进行触发,举一个例子:

lines = sc.textFile("README.md") #创建RDD,但是不会加载数据到内存中
python_lines = lines.filter(lambda line: "python" in line) # 转化成新的RDD
python_lines.first() # 并发执行计算

执行first()行动算子时,spark会只将含有python单词的第一行数据拉取到内存中,而不是全部拉取,默认情况下,Spark的RDD会在每次对他们进行行动操作时重新计算,如果想要在多个行动中使用同一个RDD的话,需要对RDD进行持久化操作。

创建RDD

两种方式:

  • 取读外部数据
  • 在驱动器程序中对一个集合进行并行化
lines = sc.parallelize(["pandas","I like python"]) # 开发时用的不多,因为需要将所有集合数据拉到一个节点上

RDD操作

两种操作:

  • 转化操作:惰性操作,返回的一定是新的RDD。
  • 行动操作:非惰性操作,返回的一定是其他类型的数据类型,这也是区别是否为行动操作的重要指标
errorsRDD = inputRDD.filter(lambda x: "error" in x)
warningsRDD = inputRDD.filter(lambda x: "warning" in x)
badlinesRDD =  errorsRDD.union(warningsRDD) # 连接操作

上述代码中,spark会使用谱系图来记录这些不同RDD之间的依赖关系,当持久化的RDD缺失数据时,spark也是使用谱系图来进行RDD的恢复的。

在这里插入图片描述

转化操作时惰性的,所以在进行转化操作时,操作程序不会立即执行,所有我们最好不要把RDD看作存储某些特定数据的数据集合,我们可以把它看成存储记录如何计算数据的指令列表。

转化操作算子

map():接收一个函数,把这个函数用于RDD中的每个元素,将函数的返回结果作为结果RDD中的元素。

nums = sc.parallelize([1,2,3,4])
squared = nums.map(lambda x: x*x) # squared: 1,4,9,16

filter():接收一个函数,将RDD中满足该函数的元素放入新的RDD中返回。

words = sc.parallelize(["python","java","C","C++"])
squared = words.map(lambda x: "C" in x) # squared: "C","C++"

flatMap():接收一个函数,应用于RDD中的每个元素并将返回的所有元素构成新的RDD.

lines = sc.parallelize(["python R beautiful","java is good","C","C++ well"])
words = lines.flatMap(lambda x: x.split(" ")) # 'python', 'R', 'beautiful', 'java', 'is', 'good', 'C', 'C++', 'well'

distinct():去重

sample():对RDD进行采样

union():生成一个包含两个RDD中所有元素的RDD

intersection():求两个RDD共同的元素的RDD

subtract():移除一个RDD中的内容

cartesian():与另一个RDD的笛卡尔积

num1 = sc.parallelize([1,2,3])
num2 = sc.parallelize([3,4,5])
num_all = num1.union(num2) # 1,2,3,3,4,5
num_dis = num_all.distinct() # 1,2,3,4,5
num_samp = num_all.sample(False,0.6) # 不确定的2个数
num_inter = num1.intersection(num2) # 3
num_sub = num1.subtract(num2) # 1,2
num_car = num1.cartesian(num2) # (1,3),(1,4)...(3,5)
行动操作算子(常用)

reduce():接收一个函数,操作两个相同类型的元素,并返回一个同样类型的元素。

collect():返回RDD中所有元素,需要将集群上的数据拉取到单节点上。

count():返回RDD中元素的个数

countByValue():各个元素在RDD中出现的次数

take(num):从RDD中返回num个元素

top(num):从RDD中返回最前面的num个元素

aggregate(zeroValue)(seq0p,comb0p):与reduce类似,不同之处在于返回不同类型的数据。理解起来很难,可以参考此链接文章

RDD持久化

RDD是惰性求值的,当重复的使用同一个RDD时,每次都会重算RDD以及它的所有依赖,消耗很大,需要对RDD进行持久化

RDD.persist()
RDD.cache()

在这里插入图片描述

参数末尾加上“_2”表示持久化数据分为两份

当缓存的数据过多,内存存不下时,spark会采用最近最少使用的缓存策略从内存中清除数据,若仅是内存缓存级别,会直接清除,下次使用,重新计算,若是磁盘级别,会将移除的数据存入磁盘中。

键值对RDD操作

spark中包括键值对类型的RDD叫做键值对RDD

以键值对集合{(1,2),(3,4),(3,6)}为例

在这里插入图片描述

在这里插入图片描述

数据读取与保存

文本文件: 每一行都是RDD的一个元素

input = sc.textFile("file:///home/README.md")

result.saveAsTextFile(outputFile)

json:读取json数据时,常常需要将其所在文件读入,然后使用json解析器来对json进行解析。

此外还支持csv、sequenceFile、对象文件、hdsf、hbase、hive等数据的读取与写入。

集群环境下的spark

在分布式环境下,spark集群采用的是主/从结构,有一个节点负责中央协调(driver节点),调度各个分布式的工作节点(执行器)。
在这里插入图片描述

驱动器节点:spark驱动器就是执行程序中的main()方法的进程。两大职责:

  • 驱动器程序负责把用户程序转化为多个物理执行的单元(task任务),其实相当与由操作指令和逻辑组成了一个有向无环图(DAG),然后按照这个DAG执行物理计划。
  • 调度执行器节点,根据DAG,驱动器程序会在各个执行器进程间协调任务的调度。驱动器会根据当前的执行点集合,尝试把所有任务基于数据所在位置分配给合适的执行器进程,从而减少网络消耗。

执行器节点:spark执行器是一种工作进程,负责在spark作业中运行任务,任务间相互独立。两大职责:

  • 负责运行组成spark应用的任务,并将结果返回给驱动器进程。
  • 他们通过自身的块管理器为用户程序中要求缓存的RDD提供内存式存储。

小结:

  1. 用户通过spark2-submit脚本提交应用。
  2. spark2-submit脚本启动驱动器程序,调用用户定义的main()方法。
  3. 驱动器程序与集群管理器通信,申请资源以启动执行器节点。
  4. 集群管理器为驱动器程序启动执行器节点。
  5. 驱动器进程执行用户应用中的操作。根据程序中所定义的对RDD的转化操作和行动操作,驱动器节点把工作以任务的形式发送到执行器进程。
  6. 任务在执行器程序中进行计算并保存结果。
  7. 如果驱动器程序的main()方法退出,或者调用了sparkContext.stop(),驱动器程序会终止执行器进程,并且通过集群管理器释放资源。

spark2-submit部署应用

bin/spark2-submit [optinos] <app jar | python file> [app options]

在这里插入图片描述

spark调优与调试

每个sparkContext对象都需要一个sparkConf的实例,sparkConf的三个创建方式。

  • spark默认配置项。
  • 代码层次创建的对象conf = new SparkConf() conf.set("spark.app.name", "my spark app")
  • 使用spark-submit传递的参数。

实际配置生效优先级:2 > 3 > 1

并行度优化

RDD会被分为一系列的分区,每个分区都是整个数据的子集,当spark调度并运行任务时,spark会为每个分区中的数据创建出一个任务,因此调整分区的数量提升并行度来提高程序的效率。两种调整分区的方法:

  • 在数据混洗操作时,使用参数的方式为混洗后的RDD指定并行度。
  • 对已有的RDD进行重新分区来获得更多或者更少的分区数RDD.repartition(num)

序列化格式

当Spark需要通过网络传输数据,或是将数据溢写到磁盘上时,spark需要把数据序列化为二进制格式,默认情况,spark会使用java内建的序列化库,在这一点上,我们可以使用其他的更好的序列化工具库,如Kryo

内存管理

spark中内存主要有以下几种用途:

  • RDD存储:当进行RDD持久化时,RDD的分区会被缓存到内存中。
  • 数据清洗与聚合的缓存区:当进行数据混洗操作时,spark会创建一些缓冲区来存储输出数据或聚合操作的中间结果,可以通过spark.shuffle.memoryFraction参数来限定缓存区内存占总内存的比例。
  • 用户代码:用户代码中定义的函数,数组及对象等会占用大部分内存。

默认情况下,spark的内存分配方案:RDD存储:60%;缓存区:20%;用户代码:20%。

内存优化的三种方案:

  • 根据需要调整各个内存占比。
  • 进行RDD持久化时,选择最优的缓存等级,若内存占用较大,可以采用MEMORY_AND_DISK(溢出后缓存到磁盘上)的等级。
  • 当内存占用较大时,缓存序列化后的对象,而不是要直接缓存,虽然先序列化会浪费一部分时间,但是也比需要时从新根据谱系图重新计算要快。

Spark SQL介绍

spark SQL 是用来操作结构化和半结构化数据的接口,提供以下三大功能:

  • 可以从各种结构化数据源中读取数据。
  • 不仅支持在spark程序内使用sql语句进行数据查询,也支持外部工具中通过标准数据库连接器(JDBC/ODBC)进行查询。
  • spark SQL 支持SQL与常规的python/java/scala代码高度整合。
### 下面介绍一下写pyspark SQL脚本的一些语法
from pyspark.sql import SparkSession # 引入pyspark sql 包
import pyspark.sql.functions as F # 导入pyspark sql 中相关函数
from pyspark.sql.window import Window # 导入开窗函数,数据清洗时需要用到
import argparse # 导入获取执行python脚本运行参数的包
from pyspark.sql.types import * # 导入使用自定义函数udf时,用的返回类型

# 获取脚本执行时需要传递的参数
paraser = argparse.ArgumentParser()
paraser.add_argument('-date','--stat_dt') # 添加参数命--stat_dt 或 -date的参数
args = paraser.parse_parse_args()
stat_dt = args.stat_dt # 得到stat_dt参数值

# 定义一个函数,作为udf函数参数使用
def get_test_data(name):
    if name == "python":
        return 1
    else:
        return 0
 
#定义udf函数
udf_get_test_data = F.udf(get_test_data,IntegerType()) #注意函数名不要(),后面参数为函数返回值													   # 的类型
# 获取一个sparkSession对象
spark = SparkSession.builder.appName("my test app")
		.config("hive.exec.dynamic.partition","true") \ # 设置开启动态分区
    	.config("hive.exec.dynamic.partition.mode","nonstrict") \ # 全部动态分区
        .enableHiveSupport() \ 
        .getOrCreate()
        
# 获取hive中数据
data = spark.sql("select cust_id,name,price,stat_dt from test_table") # 获取hive上相关字段数据
data = data.filter(data.stat_dt == stat_dt) # 过滤其他数据
data = data.withColumn("class", udf_get_test_data(F.col("name"))) # 添加一列
data = data.withColunm("price_sum", F.sum("price")).over(Window.partition("cust_id")) # 算出每个客户的总价值,使用groupBy会丢失数据
data = data.withColumn("index", F.row_number().over(Window.partitionBy("cust_id").orderBy("price"))) # 未每个客户按照价格排序
data = data.filter(data.index = F.lit("1")) # 获取每个客户价格最低的数据
data = data.drop("stat_dt") # 删除分区列
data.createOrReplaceTempView("data") # 注册为临时表
spark.sql("alter table standard.test_table_result drop if exists partition(stat_dt='%s')" %(stat_dt)) # 删除原有分区数据,支持重跑
spark.sql("insert into standard.test_table_result partition(stat_dt='%s')" %(stat_dt)) # 插入结果数据
spark.stop() # 停止程序

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值