spark学习部分笔记

每个 Spark 应用都由一个驱动器程序(driver program)来发起集群上的各种并行操作。驱动器程序包含应用的 main 函数,
驱动器程序通过一个 SparkContext 对象来访问 Spark。
调用了sc.textFile() 来创建一个代表文件中各行文本的 RDD
驱动器程序一般要管理多个执行器(executor)节点。


val conf = new SparkConf().setMaster("local").setAppName("My App")
集群 URL:告诉 Spark 如何连接到集群上。在这几个例子中我们使用的是 local ,这个
特殊值可以让 Spark 运行在单机单线程上而无需连接到集群。
应用名:在例子中我们使用的是 My App 。当连接到一个集群时,这个值可以帮助你在
集群管理器的用户界面中找到你的应用。

RDD 支持两种类型的操作:转化操作(transformation)和行动操作(action)
转化操作会由一个 RDD 生成一个新的 RDD。
行动操作会对 RDD 计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统(如 HDFS)


默认情况下,Spark 的 RDD 会在你每次对它们进行行动操作时重新计算。
如果想在多个行动操作中重用同一个 RDD,可以使用 RDD.persist() 让 Spark 把这个 RDD 缓存下来。


总的来说,每个 Spark 程序或 shell 会话都按如下方式工作。
(1) 从外部数据创建出输入 RDD。
(2) 使用诸如 filter() 这样的转化操作对 RDD 进行转化,以定义新的 RDD。
(3) 告诉 Spark 对需要被重用的中间结果 RDD 执行 persist() 操作。
(4) 使用行动操作(例如 count() 和 first() 等)来触发一次并行计算,Spark 会对计算进行优化后再执行。


cache() 与使用默认存储级别调用 persist() 是一样的


创建 RDD 最简单的方式就是把程序中一个已有的集合传给 SparkContext 的 parallelize()方法
例如 val lines = sc.parallelize(List("pandas", "i like pandas"))


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




行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作,会触发实际的计算,比如 count() 和 first() 。


如果对于一个特定的函数是属于转化操作还是行动操作感到困惑,你可以看看它的返回值类型:
转化操作返回的是 RDD,而行动操作返回的是其他的数据类型。


val inputRDD = sc.textFile("log.txt")
val errorsRDD = inputRDD.filter(line => line.contains("error"))
注意, filter() 操作不会改变已有的 inputRDD 中的数据。


从已有的 RDD 中派生出新的 RDD,Spark 会使用谱系图(lineage graph)来记录这些不同 RDD 之间的依赖关系。


行动操作是第二种类型的 RDD 操作,它们会把最终求得的结果返回到驱动器程序,或者写入外部存储系统中。


RDD 还有一个 collect() 函数,可以用来获取整个 RDD 中的数据。


记住,只有当你的整个数据集能在单台机器的内存中放得下时,才能使用 collect() ,因此, collect() 不能用在大规模数据集上。
在大多数情况下,RDD 不能通过 collect() 收集到驱动器进程中,因为它们一般都很大。
此时,我们通常要把数据写到诸如 HDFS 或 Amazon S3 这样的分布式的存储系统中。使用 saveAsTextFile() 、 saveAsSequenceFile()


不应该把 RDD 看作存放着特定数据的数据集,而最好把每个 RDD 当作我们通过转化操作构建出来的、记录何计算数据的指令列表


把需要的字段放到一个局部变量中,来避免传递包含该字段的整个对象


Scala 中出现了 NotSerializableException ,通常问题就在于我们传递了一个不可序列化的类中的函数或字段。
记住,传递局部可序列化变量或顶级对象中的函数始终是安全的。


希望对每个输入元素生成多个输出元素。实现该功能的操作叫作 flatMap() 。


 flatMap() 的一个简单用途是把输入的字符串切分为单词
 
需要注意, distinct() 操作的开销很大,因为它需要将所有数据通过网络进行混洗(shuffle),以确保每个元素都只有一份。


 union(other) ,它会返回一个包含两个 RDD 中所有元素的 RDD
 如果输入的 RDD 中有重复数据,Spark 的 union() 操作也会包含这些重复数据
 intersection()在运行时也会去掉所有重复的元素(单个 RDD 内的重复元素也会一起移除)
 subtract(other) 函数接收另一个 RDD 作为参数,返回一个由只存在于第一个 RDD 中而不存在于第二个 RDD 中的所有元素组成的 RDD。
 cartesian(other) 转化操作会返回,所有可能的 (a, b) 对,其中 a 是源 RDD 中的元素,而 b 则来自另一个 RDD
 
fold() 和 reduce() 都要求函数的返回值类型需要和我们所操作的 RDD 中的元素类型相同
使用 collect() 使得 RDD 的值与预期结果之间的对比变得很容易。


take(n) 返回 RDD 中的 n 个元素,并且尝试只访问尽量少的分区,因此该操作会得到一个
不均衡的集合。需要注意的是,这些操作返回元素的顺序与你预期的可能不一样。


为数据定义了顺序,就可以使用 top() 从 RDD 中获取前几个元素。 top() 会使用数据
的默认顺序,但我们也可以提供自己的比较函数,来提取前几个元素。


 mean() 和 variance() 只能用在数值 RDD 上,而 join() 只能用在键值对 RDD 上。
 
 在 Scala 和 Java 中,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中
 
 如果要缓存的数据太多,内存中放不下,Spark 会自动利用最近最少使用(LRU)的缓存策略把最老的分区从内存中移除。
 
 RDD 还有一个方法叫作 unpersist() ,调用该方法可以手动把持久化的 RDD 从缓存中移除。
 
Spark 的 Java API 让用户使用 scala.Tuple2 类来创建二元组。
这个类很简单:Java 用户可以通过 new Tuple2(elem1, elem2) 来创建一个新的二元组,并且可以通过 ._1() 和 ._2() 方法访问其中的元素。


reduceByKey() 与 reduce() 相当类似;它们都接收一个函数,并使用该函数对值进行合并。


combineByKey() 是最为常用的基于键进行聚合的函数。大多数基于键聚合的函数都是用它实现的


每个 RDD 都有固定数目的分区,分区数决定了在 RDD 上执行操作时的并行度。


切记,对数据进行重新分区是代价相对比较大的操作。


cogroup() 不仅可以用于实现连接操作,还可以用来求键的交集。除此之外,cogroup() 还能同时应用于三个及以上的 RDD。


 sortByKey() 函数接收一个叫作 ascending 的参数,表示我们是否想要让结果按升序排序(默认值为 true )
 
Spark 程序可以通过控制RDD 分区方式来减少通信开销。


分区并不是对所有应用都有好处的——比如,如果给定RDD 只需要被扫描一次,我们完全没有必要对其预先进行分区处理


partitionBy() 是一个转化操作,因此它的返回值总是一个新的 RDD,但它不会改变原来的 RDD


如果没有将 partitionBy() 转化操作的结果持久化,那么后面每次用到这个RDD 时都会重复地对数据进行分区操作。
不进行持久化会导致整个 RDD 谱系图重新求值。


在 Spark shell 中使用 partitioner 属性不仅是检验各种 Spark 操作如何影响分区方式的一种
好办法,还可以用来在你的程序中检查想要使用的操作是否会生成正确的结果


转化操作的结果并不一定会按已知的分区方式分区


会为生成的结果 RDD 设好分区方式的操作: cogroup() 、 groupWith() 、
join() 、 leftOuterJoin() 、 rightOuterJoin() 、 groupByKey() 、 reduceByKey() 、
combineByKey() 、 partitionBy() 、 sort() 、 mapValues() (如果父 RDD 有分区方式的话)、
flatMapValues() (如果父 RDD 有分区方式的话),以及 filter() (如果父 RDD 有分区方
式的话)。其他所有的操作生成的结果都不会存在特定的分区方式。


Spark 提供的 HashPartitioner 与 RangePartitioner 已经能够满足大多数用例




要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个方法。
?  numPartitions: Int :返回创建出来的分区数。
?  getPartition(key: Any): Int :返回给定键的分区编号(0 到 numPartitions-1 )。
?  equals() :Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个
方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个
RDD 的分区方式是否相同。






82页




如果你想要对多个 RDD 使用相同的分区方式,就应该使用同一个函数对象,比如一个全局函数,而不是为每个 RDD 创建一个新的函数对象。


只需要使用文件路径作为参数调用 SparkContext 中的 textFile() 函数,就可以读取一个文本文件
例如val input = sc.textFile("file:///home/holden/repos/spark/README.md")


如果文件足够小,那么可以使用 SparkContext.
wholeTextFiles() 方法,该方法会返回一个 pair RDD,其中键是输入文件的文件名。


读取 JSON 数据的最简单的方式是将数据
作为文本文件读取,然后使用 JSON 解析器来对 RDD 中的值进行映射操作。


如果你有跨行的
JSON 数据,你就只能读入整个文件,然后对每个文件进行解析


由于 Hadoop 使用了一套自定义的序列化框架,因此 SequenceFile 是由实现 Hadoop 的 Writable
接口的元素组成


 SequenceFile 存储的
是键值对,所以需要创建一个由可以写出到 SequenceFile 的类型构成的 PairRDD 




hadoopDataset /
saveAsHadoopDataSet 和 newAPIHadoopDataset / saveAsNewAPIHadoopDataset 来访问 Hadoop 所
支持的非文件系统的存储格式。例如,许多像 HBase 和 MongoDB 这样的键值对存储都提
供了用来直接读取 Hadoop 输入格式的接口。


Spark 原生的输入方式( textFile 和 sequenceFile )可以自动处理一些类型的压缩。


推荐的方法是将文件先放到像 HDFS、NFS、S3 等共享文件系统上。


在 Java 和 Scala 中, Row 对象的访问是基于下标的。每个 Row 都有一个
get() 方法,会返回一个一般类型让我们可以进行类型转换。


Spark 可 以 通 过
Hadoop 输入格式访问 HBase。这个输入格式会返回键值对数据,其中键的类型为 org.
apache.hadoop.hbase.io.ImmutableBytesWritable ,而值的类型为 org.apache.hadoop.hbase.
client.Result 。




累加器(accumulator)与广播变量(broadcast variable)。累加器用来对信息进行聚合,而
广播变量用来高效分发较大的对象。


Spark 的两个共享变量,累加器与广
播变量,分别为结果聚合与广播这两种常见的通信模式突破了这一限制。




总结起来,累加器的用法如下所示。
1.通过在驱动器中调用 SparkContext.accumulator(initialValue) 方法,创建出存有初
始值的累加器。返回值为 org.apache.spark.Accumulator[T] 对象,其中 T 是初始值
initialValue 的类型。
2.Spark闭包里的执行器代码可以使用累加器的 += 方法(在Java中是 add )增加累加器的值。
3. 驱动器程序可以调用累加器的 value 属性(在 Java 中使用 value() 或 setValue() )来访
问累加器的值。
注意,工作节点上的任务不能访问




注意,工作节点上的任务不能访问累加器的值。
Spark 会自动重新执行失败的或较慢的任务来应对有错误的或者比较慢的机器


对于要在行动操作中使用的累加器,Spark
只会把每个任务对各累加器的修改应用一次。
如果想要一个无论在失败还是重复计
算时都绝对可靠的累加器,我们必须把它放在 foreach() 这样的行动操作中。
对于在 RDD 转化操作中使用的累加器,就不能保证有这种情况了。




使用广播变量的过程很简单。
(1) 通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。
任何可序列化的类型都可以这么实现。
(2) 通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。
(3) 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。




基于分区对数据进行操作可以让我们避免为每个数据元素进行重复的配置工作。




通过 pipe() ,你可以
将 RDD 中的各元素从标准输入流中以字符串形式读出,并对这些元素执行任何你需要
的操作,然后把结果以字符串的形式写入标准输出——这个过程就是 RDD 的转化操作过
程。


任务是 Spark 中最小的工作单
元,用户程序通常要启动成百上千的独立任务。


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


执行器进
程有两大作用:第一,它们负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程;
第二,它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内
存式存储。




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


从这看出,spark的driver和exctor是逻辑概念。
而master和worker是关于并行化集群的相对具体
的概念。


Spark 文档中始终使用 驱动器节点 和 执行器节点 的概念来描述执行 Spark
应用的进程。而 主节点 (master)和 工作节点 (worker)的概念则被用来
分别表述集群管理器中的中心化的部分和分布式的部分。






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




事实上,常规的做法是使用构建工具,生成单个大 JAR 包,包含应用的所有的传递依赖。这通常被称为超级(uber)JAR 或者组合(assembly) JAR




通常,依赖冲突表现为 Spark 作业执行过
程中抛出 NoSuchMethodError 、 ClassNotFoundException ,或其他与类加载相关的 JVM 异
常。对于这种问题,主要有两种解决方式:一是修改你的应用,使其使用的依赖库的版本
与 Spark 所使用的相同,二是使用通常被称为“shading”的方式打包你的应用。




阻碍应用运行的一个常见陷阱是为执行器进程申请的内存( spark-submit 的
--executor-memory 标记传递的值)超过了集群所能提供的内存总量。在这种
情况下,独立集群管理器始终无法为应用分配执行器节点。请确保应用申请
的值能够被集群接受。




有两种将应用连接到集群的模式:客户端模式以及集群模式。在
客户端模式下应用的驱动器程序运行在提交应用的机器上(比如你的笔记本电脑),而在
集群模式下,驱动器程序也运行在一个 YARN 容器内部




在“细粒度”模式(默认)中,执行器进程占用的 CPU 核心数会在它们执行任务时
动态变化,因此一台运行了多个执行器进程的机器可以动态共享 CPU 资源。而在“粗粒
度”模式中,Spark 提前为每个执行器进程分配固定数量的 CPU 数目,并且在应用结束前
绝不释放这些资源,哪怕执行器进程当前不在运行任务。你可以通过向 spark-submit 传递
--conf spark.mesos.coarse=true 来打开粗粒度模式。








第八章






有向无环图(DAG)


调度器为有向图中的每个
RDD 输出计算步骤,步骤中包括 RDD 上需要应用于每个分区的任务。


特定的行动操作所生成的步骤的集合被称为一个作业。
一个物理步骤会启动很多任务,每个任务都是在不同的数据分区上做同样的事情。






Spark 执行时有下面所列的这些流程。
1.用户代码定义RDD的有向无环图RDD 上的操作会创建出新的 RDD,并引用它们的父节点,这样就创建出了一个图。
2. 行动操作把有向无环图强制转译为执行计划当你调用 RDD 的一个行动操作时,这个 RDD 就必须被计算出来。这也要求计算出该jobRDD 的父节点。Spark 调度器提交一个作业来计算所有必要的 RDD。这个作业会包含一个或多个步骤,每个步骤其实也就是一波并行执行的计算任务。一个步骤对应有向无环图中的一个或多个 RDD,一个步骤对应多个 RDD 是因为发生了流水线执行。
3.任务于集群中调度并执行 步骤是按顺序处理的,任务则独立地启动来计算出 RDD 的一部分。一旦作业的最后一个步骤结束,一个行动操作也就执行完毕了。




当看到少量的任务相对于其他任务需要花费大量时间的时候,一般就是发生了数据倾斜。


如果任务花了很少的时间读取或输出数据,但是总时间却很长,这就可能表明用户代码本身的执行比较花时间


Spark 也会针对 RDD 直接自动推断出合适的并行度,这对于大多数用例来说已经足够了。输入 RDD 一般会根据其底层的存储系统选择并行度。


第一种方法是在数据混洗操作时,使
用参数的方式为混洗后的 RDD 指定并行度。第二种方法是对于任何已有的 RDD,可以进行重新分区来获取更多或者更少的分区数。


在默认情况下,Spark 会使用 60%的空间来存储 RDD,20% 存储数据混洗操作产生的数
据,剩下的 20% 留给用户程序。用户可以自行调节这些选项来追求更好的性能表现。如果
用户代码中分配了大量的对象,那么降低 RDD 存储和数据混洗存储所占用的空间可以有
效避免程序内存不足的情况。


切记,“越多越好”的原则在设置执行器节点内存时并不一定适用。




Spark SQL 提供了以下三大功能
(1) Spark SQL 可以从各种结构化数据源(例如 JSON、Hive、Parquet 等)中读取数据。
(2) Spark SQL 不仅支持在 Spark 程序内使用 SQL 语句进行数据查询,也支持从类似商业智能软件 Tableau 这样的外部工具中通过标准数据库连接器(JDBC/ODBC)连接 SparkSQL 进行查询。
(3) 当在 Spark 程序内使用 Spark SQL 时,Spark SQL 支持 SQL 与常规的 Python/Java/Scala代码高度整合,包括连接 RDD 与 SQL 表、公开的自定义 SQL 函数接口等。这样一来,许多工作都更容易实现了。


 1 SchemaRDD是存放 Row 对象的 RDD,每个 Row 对象代表一行记录。
 
 Spark SQL 编译时可以包含 Hive 支持,也可以不包含。
 
 需要强调的一点是,如果要在 Spark SQL 中包含 Hive 的库,并不需要事先安装 Hive。
 
 如果你是从代码编译Spark,你应该使用 sbt/sbt -Phive assembly 编译,以打开 Hive 支持。 
 
 如果你不能引入 Hive 依赖,那就应该使用工件 spark-sql_2.10 来代替 spark-hive_2.10 。
 
 
  HiveContext ,它可以提供 HiveQL 以及其他依赖于 Hive 的功能的支持。更为基础的 SQLContext 则支持 Spark SQL 功能的一个子集,子集中去掉了需要依赖于 Hive 的功能。
  
  即使没有部署好 Hive,Spark SQL 也可以运行。
  
这里没有用类似在导入 SparkContext 时的方法那样导入HiveContext._ 来访问隐式转换。隐式转换被用来把带有类型信息的 RDD 转变为专门用于Spark SQL 查询的 RDD(也就是 SchemaRDD)。


SchemaRDD 是一个由 Row 对象组成的 RDD,附带包含每列数据类型的结构信息。 Row 对象只是对基本数据类型(如整型和字符串型等)的数组的封装。


今后的 Spark 版本中(1.3 及以后),SchemaRDD 这个名字可能会被改为 DataFrame。


 hiveCtx.cacheTable("tableName") 方法
 
Spark Streaming 使用离散化流(discretized stream)作为抽象表示,叫作 DStream。


DStream 可以从各种输入源创建,比如 Flume、Kafka 或者 HDFS。创
建出来的 DStream 支持两种操作,一种是转化操作(transformation),会生成一个新的
DStream,另一种是输出操作(output operation),可以把数据写入外部系统中。




Spark Streaming 应用需要进行额外配置来保证 24/7 不间断工作 (24/7 是一天24小时,每周7天)


Spark Streaming 程序最好以使用 Maven 或者 sbt 编译出来的独立应用的形式运行。


必须显式调用 StreamingContext 的 start() 方法


请注意,一个 Streaming context 只能启动一次,所以只有在配置好所有 DStream 以及所需要的输出操作之后才能启动。


时间区间的大小是由批次间隔这个参数决定的。批次间隔一般设在 500 毫秒到几秒之间,由应用开发者配置。


DStream它是一个 RDD 序列,每个 RDD 代表数据流中一个时间片内的数据


Spark Streaming 为每个输入源启动对应的接收器


般来说,你需要每处理 5-10 个批次的数据就保存一次。在恢复数据时,Spark Streaming 只需要回溯到上一个检查点即可


每个 DStream 在内部是由许多 RDD(批次)组成,且无状态转化操作是分别应用到每个 RDD 上的。例如,reduceByKey()
 会归约每个时间区间中的数据,但不会归约不同区间之间的数据。
 
 
K-means 中最重要的参数是生成的聚类中心的目标数量 K。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值