Hadoop10-Spark初步认识

1. Spark简介

数据分布式。

操作并行化,Spark 会自动将函数(比如 line.contains("Python"))发到各个执行器节点上。这样,你就可以在单一的
驱动器程序中编程,并且让代码自动运行在多个节点上。

在分布式环境下, Spark 集群采用的是主 / 从结构。在一个 Spark 集群中,有一个节点负责中央协调, 调度各个分布式工作节点。这个中央协调节点被称为驱动器( Driver) 节点,与之对应的工作节点被称为工作( worker) 节点。驱动器节点可以和大量的执行器节点进行通信, 它们也都作为独立的 Java 进程运行。驱动器节点和所有的执行器节点一起被称为一个 Spark 应用( application)。Spark 应用通过一个叫作集群管理器( Cluster Manager)的外部服务在集群中的机器上启动。

Driver

Spark 驱动器是执行你的程序中的 main() 方法的进程。它执行用户编写的用来创建SparkContext、创建 RDD,以及进行 RDD 的转化操作和行动操作的代码。

Driver主要有两个作用

  • 把用户程序转为任务
  • 为执行器节点调度任务

ClusterManager

驱动器节点和执行器节点是如何启动的呢? Spark 依赖于集群管理器来启动执行器节点,而在某些特殊情况下,也依赖集群管理器来启动驱动器节点。

Worker

Worker的作用

  • 它们负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程;
  • 它们通过自身的块管理器( Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。 RDD 是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加速运算。

2. Spark环境搭建

3. Spark运行模式

4. RDD简介

4.1 RDD(弹性分布式数据,分布式数据)

弹性的意思是当保存RDD的一台机器遇到错误时,Spark可以根据lineage谱系图重新计算出这些RDD。

分布式的意思是,这些对象集合(分区)被分布式的存储在集群中的不同节点上。

Spark 中的 RDD 就是一个不可变的分布式对象集合。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。

4.2 创建RDD

用户可以使用两种方法创建 RDD: 读取一个外部数据集,或在驱动器程序里分发驱动器程序中的对象集合( 比如 list 和 set)。

1. 创建 RDD 最简单的方式就是把程序中一个已有的集合传给 SparkContext 的 parallelize()方法。

lines = sc.parallelize(["pandas", "i like pandas"])

2. 更常用的方式是从外部存储中读取数据来创建 RDD。

lines = sc.textFile("/path/to/README.md")

4.3 RDD操作

Transformation和Action

4.3.1 Transformation-从已有的 RDD 创建出新的 RDD,惰性计算

假定我们有一个日志文件 log.txt, 内含有若干消息,希望选出其中的错误消息。我们可以使用前面说过的转化操作 filter()

inputRDD = sc.textFile("log.txt")
errorsRDD = inputRDD.filter(lambda x: "error" in x)

filter() 操作不会改变已有的 inputRDD 中的数据。该操作会返回一个全新的 RDD。 inputRDD 在后面的程序中还可以继续使用,比如我们还可以从中搜索别的单词。

接下来,使用另一个转化操作 union() 来打印出包含 error 或 warning 的行数。

errorsRDD = inputRDD.filter(lambda x: "error" in x)
warningsRDD = inputRDD.filter(lambda x: "warning" in x)
badLinesRDD = errorsRDD.union(warningsRDD)

通过转化操作,你从已有的 RDD 中派生出新的 RDD, Spark 会使用谱系图( lineage graph)(很重要的概念,RDD的容错策略之一)来记录这些不同 RDD 之间的依赖关系。 Spark 需要用这些信息来按需计算每个 RDD, 也可以依靠谱系图在持久化的 RDD 丢失部分数据时恢复所丢失的数据。

4.3.2 Action-它们会把最终求得的结果返回到驱动器程序, 或者写入外部存储系统中

继续我们之前用到的日志的例子, 我们可能想输出关于 badLinesRDD 的一些信息。为此,需要使用两个行动操作来实现:用 count() 来返回计数结果,用 take() 来收集RDD 中的一些元素

print("Input had " + badLinesRDD.count() + " concerning lines")
print("Here are 10 examples:")
#打印10条记录
for line in badLinesRDD.take(10):
    print(line)

我们在驱动器程序中(Driver)使用 take() 获取了 RDD 中的少量元素。然后在本地遍历这些元素, 并在驱动器端打印出来。 RDD 还有一个 collect() 函数,可以用来获取整个 RDD 中的数据。如果你的程序把 RDD 筛选到一个很小的规模,并且你想在本地处理这些数据时, 就可以使用它。记住,只有当你的整个数据集能在单台机器的内存中放得下时,才能使用 collect(),因此, collect() 不能用在大规模数据集上。大多数情况下,RDD都很大,此时可以使用 saveAsTextFile()、 saveAsSequenceFile(),或者任意的其他行动操作来把 RDD 的数据内容以各种自带的格式保存起来。需要注意的是, 每当我们调用一个新的Action操作时,整个 RDD 都会从头开始计算。要避免这种低效的行为,可以将中间结果持久化。

4.3.3 惰性求值

我们不应该把 RDD 看作存放着特定数据的数据集, 而最好把每个 RDD 当作我们通过转化操作构建出来的、记录如何计算数据的指令列表。 把数据读取到 RDD 的操作也同样是惰性的。因此,当我们调用sc.textFile() 时,数据并没有读取进来,而是在必要时才会读取。和转化操作一样的是,读取数据的操作也有可能会多次执行。Spark 使用惰性求值,这样就可以把一些操作合并到一起来减少计算数据的步骤。在类似Hadoop MapReduce 的系统中,开发者常常花费大量时间考虑如何把操作组合到一起,以减少 MapReduce 的周期数。而在 Spark 中,写出一个非常复杂的映射并不见得能比使用很多简单的连续操作获得好很多的性能。 因此,用户可以用更小的操作来组织他们的程序,这样也使这些操作更容易管理。

4.3.4 RDD的API简介

Spark 的大部分转化操作和一部分行动操作,都需要依赖用户传递的函数来计算。

有三种方式来把函数传递给 Spark。传递比较短的函数时,可以使用lambda 表达式来传递, 如例WordCount 所示。除了 lambda 表达式,我们也可以传递顶层函数或是定义的局部函数。Python 会在你不经意间把函数所在的对象也序列化传出去。当你传递的对象是某个对象的成员, 或者包含了对某个对象中一个字段的引用时(例如 self.field), Spark 就会把整个对象发到工作节点上,这可能比你想传递的东西大得多。

传递一个带引用的函数

class SearchFunctions(object):
    def __init__(self, query):
        self.query = query
    def isMatch(self, s):
        return self.query in s
    def getMatchesFunctionReference(self, rdd):
        # 问题:在"self.isMatch"中引用了整个self
        return rdd.filter(self.isMatch)
    def getMatchesMemberReference(self, rdd):
        # 问题:在"self.query"中引用了整个self
        return rdd.filter(lambda x: self.query in x)

替代方案,把所需字段从对象中拿出来放到一个局部变量中。

class WordFunctions(object):
...
    def getMatchesNoReference(self, rdd):
        # 安全:只把需要的字段提取到局部变量中
        query = self.query
        return rdd.filter(lambda x: query in x)

5. Spark版本的WordCount

import os
# 在提交任务的时候,设置worker的python版本
os.environ["PYSPARK_PYTHON"]="/usr/local/bin/python3.7"

from pyspark import SparkConf,SparkContext

conf = SparkConf().setMaster('local').setAppName('word count')
sc = SparkContext(conf = conf)

text_file = sc.textFile('file:///Users/sheldonwong/workspace/jupyter/spark/wordcount.txt')

count = textfile.flatMap(lambda line:line.split(" ")) \
                    .map(lambda word:(word,1)) \
                    .reduceByKey(lambda a,b:a+b)
print(count.collect())

其实关键代码就一行

counts = text_file.flatMap(lambda line:line.split(" ")).map(lambda word:(word,1)).reduceByKey(lambda a,b:a+b)

首先是将文本文件读取为一个RDD

然后将RDD进行flatMap转换,也就是先对每一行,按照空格分隔,然后在押平。

其次对RDD进行map转换,将每个word映射为(word,1)。

最后进行reduceByKey()操作,将相同key的累加。

 

6. Spark实践与部署

应用的部署

默认本地执行

bin/spark-submit my_script.py

将应用提交到 Spark 独立集群上的时候,可以将独立集群的地址和希望启动的每个执行器进程的大小作为附加标记提供

--master 标记指定要连接的集群 URL

bin/spark-submit --master spark://host:7077 --executor-memory 10g my_script.py

spark-submit --master可以接收的参数

除了集群 URL, spark-submit 还提供了各种选项,可以让你控制应用每次运行的各项细节。这些选项主要分为两类。 第一类是调度信息,比如你希望为作业申请的资源量(如例7-2 所示)。第二类是应用的运行时依赖,比如需要部署到所有工作节点上的库和文件。

# 使用独立集群模式提交Java应用
$ ./bin/spark-submit \
--master spark://hostname:7077 \
--deploy-mode cluster \
--class com.databricks.examples.SparkExample \
--name "Example Program" \
--jars dep1.jar,dep2.jar,dep3.jar \
--total-executor-cores 300 \
--executor-memory 10g \
myApp.jar "options" "to your application" "go here"
# 使用YARN客户端模式提交Python应用
$ export HADOP_CONF_DIR=/opt/hadoop/conf
$ ./bin/spark-submit \
--master yarn \
--py-files somelib-1.2.egg,otherlib-4.4.zip,other-file.py \
--deploy-mode client \
--name "Example Program" \
--queue exampleQueue \
--num-executors 40 \
--executor-memory 10g \
my_script.py "options" "to your application" "go here"

7. 源码剖析

7.1 Spark任务提交流程

 

8.  抽取的关键点研究

RDD

任务提交流程

 

参考:

《Spark快速大数据分析》Matei Zaharia等

Spark Examples https://spark.apache.org/examples.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值