Spark快速大数据分析要点

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_24505127/article/details/80430542
Spark快速大数据分析要点

1、默认情况下,Spark 的 RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个 RDD,可以使用 RDD.persist() 让 Spark 把这个数据以序列化的形式缓存在 JVM 的堆空
间中。我们可以让 Spark 把数据持久化到许多不同的地方。在第一次对持久化的 RDD 计算之后,Spark 会把 RDD 的内容保存到内存中(以分区方式存储到集群中的各机器上),这样在之后的行动操作中,就可以重用这些数据了。 默认不进行持久化(如果不会重用该 RDD,我们就没有必要浪费存储空间)。RDD 可以使用 result.persist(StorageLevel.DISK_ONLY) 选择不同的持久化级别。 persist() 调用本身不会触发强制求值。如果要缓存的数据太多,内存中放不下,Spark 会自动利用最近最少使用(LRU)的缓存策略把最老的分区从内存中移除。对于仅把数据存放在内存中的缓存级别,下一次要用到已经被移除的分区时,这些分区就需要重新计算。但是对于使用内存与磁盘的缓存级别的分区来说,被移除的分区都会写入磁盘。RDD 还有一个方法叫作 unpersist() ,调用该方法可以手动把持久化的 RDD 从缓存中移除。

2、 ETL(抽取、转化、装载)
3、Spark 为包含键值对类型的 RDD 提供了一些专有的操作。这些 RDD 被称为 pair RDD
4、当需要把一个普通的 RDD 转为 pair RDD 时,可以调用 map() 函数来实现,传递的函数需要返回键值对:val pairs = lines.map(x => (x.split(" ")(0), x))
5、调用 reduceByKey() 和 foldByKey() 会在为每个键计算全局的总结果之前先自动在每台机器上进行本地合并。用户不需要指定合并器。更泛化的combineByKey() 接口可以让你自定义合并的行为。
6、我们希望在除分组操作和聚合操作之外的操作中也能改变 RDD 的分区。对于这样的情况,Spark 提供了 repartition() 函数。它会把数据通过网络进行混洗,并创建出新的分区集合。切记,对数据进行重新分区是代价相对比较大的操作。Spark 中也有一个优化版的 repartition() ,叫作 coalesce()
7、。在分布式程序中,通信的代价是很大的,因此控制数据分布以获得最少的网络传输可以极大地提升整体性能。和单节点的程序需要为记录集合选择合适的数据结构一样,Spark 程序可以通过控制RDD 分区方式来减少通信开销。Spark 中所有的键值对 RDD 都可以进行分区。尽管 Spark 没有给出显示控制每个键具体落在哪一个工作节点上的方法(部分原因是Spark 即使在某些节点失败时依然可以工作),但 Spark 可以确保同一组的键出现在同一个节点上
8、使用 partitionBy() 转化操作可以减少数据混洗及网络传输。使用join连接,程序开始时,对初始userDataRDD表使用 partitionBy() 转化操作,将这张表转为哈希分区。可以通过向 partitionBy 传递一个spark.HashPartitioner 对象来实现该操作。进来一批eventRdd后,当调用 userData.join(events) 时,Spark 只会对 events 进行数据混洗操作,将 events 中特定 UserID 的记录发送到 userData 的对应分区所在的那台机器上(见图 4-5)。这样,需要通过网络传输的数据就大大减少了,程序运行速度也可以显著提升了。应该对 partitionBy() 的结果进行持久化,并保存为 userData。不进行持久化会导致整个 RDD 谱系图重新求值。致重复对数据进行分区以及跨节点的混洗。
9、一个文本文件读取为 RDD 时,输入的每一行都会成为 RDD 的一个元素。也可以将多个完整的文本文件一次性读取为一个 pair RDD,其中键是文件名,值是文件内容。如果多个输入文件以一个包含数据所有部分的目录的形式出现,可以用两种方式来处理。可以仍使用 textFile 函数,传递目录作为参数,如果文件足够小,那么可以使用 SparkContext.wholeTextFiles() 方法,该方法会返回一个 pair RDD,其中键是输入文件的文件名
10,读取JSON,文件中的每一行都是一条 JSON 记录。如果你有跨行的JSON 数据,你就只能读入整个文件,然后对每个文件进行解析。
11、定义一个累加变量(全局变量):# 创建Accumulator[Int]并初始化为0:  blankLines = sparkContext.longAccumulator("ErroNum"),Spark闭包里的执行器代码可以使用累加器的 += 方法(在Java中是 add )增加累加器的值.累加器是一个只写变量
12、Spark 会自动重新执行失败的或较慢的任务来应对有错误的或者比较慢的机器。例如,如果对某分区执行 map() 操作的节点失败了,Spark 会在另一个节点上重新运行该任务。即使该节点没有崩溃,而只是处理速度比别的节点慢很多,Spark 也可以抢占式地在另一个节点上启动一个“投机”(speculative)型的任务副本,如果该任务更早结束就可以直接获取结果。即使没有节点失败,Spark 有时也需要重新运行任务来获取缓存中被移除出内存的数据。因此最终结果就是同一个函数可能对同一个数据运行了多次。
13、 因为Spark 相同的Task有可能被重复执行多次(容错性导致),所以累加器的值很有可能被累加多次,那么得到的结果就不准确了,所以一般把累加器放在行动操作中来使用,只有这样,Spark才会把每个任务对各累加器的修改应用一次
14、持久化persist:要持久化一个RDD,只要调用其cache()或者persist()方法即可。在该RDD第一次被计算出来时,就会直接缓存在每个节点中。
15、共享变量——广播变量Broadcast:通常情况下,当一个RDD的很多操作都需要使用driver中定义的变量时,每次操作,driver都要把变量发送给worker节点一次,如果这个变量中的数据很大的话,会产生很高的传输负载,导致执行效率降低。使用广播变量可以使程序高效地将一个很大的只读数据发送给多个worker节点,而且对每个worker节点只需要传输一次,每次操作时executor可以直接获取本地保存的数据副本,不需要多次传输。val signPrefixes = sc.broadcast(loadCallSignTable()),通过signPrefixes.value取值。应作为只读值处理(修改这个值不会影响到别的节点)
16、共享变量——累加器Accumulator。 它会在一个远程集群节点上执行,它会使用函数中所有变量的副本。这些变量被复制到所有的机器上,远程机器上并没有被更新的变量会向驱动程序回传.
17、将连续的映射转为流水线化执行,将多个操作合并到一个步骤中等。这样 Spark 就把逻辑计划转为一系列步骤(stage)。而每个步骤又由多个任务组成。这些任务会被打包并送到集群中。任务是 Spark 中最小的工作单元,用户程序通常要启动成百上千的独立任务
18、任务是 Spark 中最小的工作单元,用户程序通常要启动成百上千的独立任务。Spark 程序其实是隐式地创建出了一个由操作组成的逻辑上的有向无环图(Directed Acyclic Graph,简称 DAG)。当驱动器程序运行时,它会把这个逻辑图转为物理执行计划。在集群上运行 Spark | 103Spark 会对逻辑执行计划作一些优化,比如将连续的映射转为流水线化执行,将多个操作合并到一个步骤中等。这样 Spark 就把逻辑计划转为一系列步骤(stage)。而每个步骤又由多个任务组成。这些任务会被打包并送到集群中。任务是 Spark 中最小的工作单元,用户程序通常要启动成百上千的独立任务。
19、Excuter执行器进程有两大作用:第一,它们负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程;第二,它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储
20、作业提交参数说明

21、如果你的程序引入了任何既不在 org.apache.spark 包内也不属于语言运行时的第三方的库的依赖,用户可以通过 spark-submit 的 --jars 标记提交独立的 JAR 包依赖。向 Spark 提交应用时,你必须把应用的整个依
赖传递图中的所有依赖都传给集群。你不仅要传递你直接依赖的库,还要传递这些库的依赖,以及它们的依赖的依赖,等等。手动维护和提交全部的依赖 JAR 包是很笨的方法。事实上,常规的做法是使用构建工具,生成单个大 JAR 包,包含应用的所有的传递依赖。这通常被称为超级(uber)JAR 或者组合(assembly) JAR。
22、现实中,许多集群是在多个用户间共享的。共享的环境会带来调度方面的挑战:如果两个用户都启动了希望使用整个集群所有资源的应用,该如何处理呢? Spark 有一系列调度策略来保障资源不会被过度使用,还允许
工作负载设置优先级。Spark 提供了一种用来配置应用内调度策略的机制。Spark 内部的公平调度器(Fair Scheduler)会让长期运行的应用定义调度任务的优先级队列
23、spark-submit 也支持从文件中读取配置项的值。这对于设置一些与环境相关的配置项比Spark调优与调试较有用,方便不同用户共享这些配置(比如默认的 Spark 主节点)。默认情况下, spark-submit 脚本会在 Spark 安装目录中找到 conf/spark-defaults.conf 文件,尝试读取该文件中以空格隔开的键值对数据。你也可以通过 spark-submit 的 --properties-File 标记,自定义该文件的路径,运行时使用默认文件设置配置项的值。同一个配置项可以在多个地方被设置。一旦传给了 SparkContext 的构造方法,应用所绑定的 SparkConf 就不可变了。这意味着所有的配置项都必须在 SparkContext 实例化出来之前定下来。

$ bin/spark-submit \
--class com.example.MyApp \
--properties-file my-config.conf \
myApp.jar

## Contents of my-config.conf ##
spark.master local[4]
spark.app.name "My Spark App"
spark.ui.port 36000
24、当你在 RDD 上调用 val b = a.map() 时, b 这个 RDD 就存下了对其父节点 a 的一个引用。这些引用使得 RDD 可以追踪到其所有的祖先节点。Spark 提供了 toDebugString() 方法来查看 RDD 的谱系。
25、任务于集群中调度并执行步骤是按顺序处理的,任务则独立地启动来计算出 RDD 的一部分。一旦作业的最后一个步骤结束,一个行动操作也就执行完毕了
26、如果要在 Spark SQL 中包含 Hive 的库,并不需要事先安装 Hive。一般来说,最好还是在编译 Spark SQL 时引入 Hive 支持,这样就可以使用这些特性了。如果你下载的是二进制版本的 Spark,它应该已经在编译时添加了 Hive 支持。而如果你是从代码编译Spark,你应该使用 sbt/sbt -Phive assembly 编译,以打开 Hive 支持
27、如果你没有部署好 Hive,Spark SQL 会在当前的工作目录中创建出自己的Hive 元数据仓库,叫作 metastore_db 。此外,如果你尝试使用 HiveQL 中的 CREATE TABLE(并非 CREATE EXTERNAL TABLE )语句来创建表,这些表会被放在你默认的文件系统中的/user/hive/warehouse 目录中(如果你的 classpath 中有配好的 hdfs-site.xml,默认的文件系统就是 HDFS,否则就是本地文件系统)
28、临时表是当前使用的 HiveContext 或 SQLContext 中的临时变量,在你的应用退出时这些临时表就不再存在了。
29、Spark Streaming要开始接收数据,必须显式调用 StreamingContext 的 start() 方法。这样,Spark Streaming 就会开始把 Spark 作业不断交给下面的 SparkContext 去调度执行。执行会在另一个线程中进行,所以需要调用awaitTermination 来等待流计算完成,来防止应用退出。
30、Spark Streaming 使用“微批次”的架构,把流式计算当作一系列连续的小规模批处理来对待。Spark Streaming 从各种输入源中读取数据,并把数据分组为小的批次。新的批次按均匀的时间间隔创建出来。在每个时间区间开始的时候,一个新的批次就创建出来,在该区间内收到的数据都会被添加到这个批次中。在时间区间结束时,批次停止增长。时间区间的大小是由批次间隔这个参数决定的。批次间隔一般设在 500 毫秒到几秒之间,由应用开发者配置。每个 RDD 代表数据流中一个时间片内的数据每个输入批次都形成一个 RDD,以 Spark 作业的方式处理并生成其他的 RDD。处理的结果可以以批处理的方式传给外部系统。(Tranform阶段可将获取的DStream看成一个RDD做处理)
                   
31、Spark Streaming 为每个输入源启动对应的接收器。接收器以任务的形式运行在应用的执行器进程中,从输入源收集数据并保存为 RDD。它们收集到输入数据后会把数据复制到另一个执行器进程来保障容错性(默认行为)。
                
32、Spark Streaming 对 DStream 提供的容错性与 Spark 为 RDD 所提供的容错性一致:只要输入数据还在,它就可以使用 RDD 谱系重算出任意状态(比如重新执行处理输入数据的操作)。默认情况下,收到的数据分别存在于两个节点上,这样 Spark 可以容忍一个工作节点的故障。不过,如果只用谱系图来恢复的话,重算有可能会花很长时间,因为需要处理从程序启动以来的所有数据。因此,Spark Streaming 也提供了检查点机制,可以把状态阶段性地存储到可靠文件系统中(例如 HDFS 或者 S3)。一般来说,你需要每处理 5-10 个批次的数据就保存一次。在恢复数据时,Spark Streaming 只需要回溯到上一个检查点即可
33、DStream 的转化操作可以分为无状态(stateless)和有状态(stateful)两种。在无状态转化操作中,每个批次的处理不依赖于之前批次的数据,有状态转化操作需要使用之前批次的数据或者是中间结果来计算当前批次的数据。有状态转化操作包括基于滑动窗口的转化操作和追踪状态变化的转化操作
34、 DStream 在内部是由许多 RDD(批次)组成,且无状态转化操作是分别应用到每个 RDD 上的,例如,reduceByKey() 会归约每个时间区间中的数据,但不会归约不同区间之间的数据
35、DStream 还提供了一个叫作 transform() 的高级操作符,可以让你直接操作其内部的 RDD。这个 transform() 操作允许你对 DStream 提供任意一个 RDD 到 RDD 的函数。这个函数会在数据流中的每个批次中被调用,生成一个新的流。 transform() 的一个常见应用就是重用你为 RDD 写的批处理代码。例如,如果你有一个叫作 extractOutliers() 的函数,用来从一个日志记录的 RDD 中提取出异常值的RDD(可能通过对消息进行一些统计),你就可以在 transform() 中重用它

//在 Scala 中对 DStream 使用 transform()
val outlierDStream = accessLogsDStream.transform { rdd =>
extractOutliers(rdd)
}
36、 window() 生成的 DStream 中的每个 RDD 会包含多个批次中的数据,如间隔10秒进来一批数据, 用window函数每10秒获取前30秒的数据。
val accessLogsWindow = accessLogsDStream.window(Seconds(30), Seconds(10))。 那么accessLogsWindow这个DStream 中的每个RDD等于前3批的RDD的结果.
37、 和网络里的滑动窗口差不多,难点在于一个普通的reduceByWindow()和使用逆函数的增量式reduceByWindow()的区别,其实二者实现的效果是一样的,只是操作的方法,普通的就是每次计算该窗口内左右的RDD然后综合信息,而有逆函数的是通过增加新进入的和去掉老的RDD来实现窗口的,对于较大的窗口,后者可以大大提高执行效率

在日志处理的例子中,我们可以使用这两个函数来更高效地对每个 IP 地址访问量进行计数,例 10-19:Scala 版本的每个 IP 地址的访问量计数

val ipDStream = accessLogsDStream.map(logEntry => (logEntry.getIpAddress(), 1))
val ipCountDStream = ipDStream.reduceByKeyAndWindow(
{(x, y) => x + y}, // 加上新进入窗口的批次中的元素
{(x, y) => x - y}, // 移除离开窗口的老批次中的元素
Seconds(30), // 窗口时长
Seconds(10)) // 滑动步长
38、Spark Streaming 可以读取Spark监控的文件系统的文件,val logData = ssc.textFileStream(logDirectory),其中logDirectory 是 Spark 监控的目录,该目录下的文件必须是以moving 或者rename 过去的文件,他不对文件内容做监控。
39、不要在本地模式下把主节点配置为 "local" 或 "local[1]" 来运行 SparkStreaming 程序。这种配置只会分配一个 CPU 核心给任务,如果接收器运行在这样的配置里,就没有剩余的资源来处理收到的数据了。至少要使用"local[2]" 来利用更多的核心。
40、Spark Streaming流式计算不间断运行的保障点:
    1、检查点机制
        检查点机制是我们在 Spark Streaming 中用来保障容错性的主要机制
        (1)控制发生失败时需要重算的状态数。Spark Streaming 可以通过转化图的谱系图来重算状态,检查点机制则可以控制需要在转化图中回溯多远。
        (2)提供驱动器程序容错。如果流计算应用中的驱动器程序崩溃了,你可以重启驱动器程序并让驱动器程序从检查点恢复,这样 Spark Streaming 就可以读取之前运行的程序处理数据的进度,并从那里继续
    2、驱动器程序容错
     驱动器程序的容错要求我们以特殊的方式创建 StreamingContext。我们需要把检查点目录提供给 StreamingContext。与直接调用 new StreamingContext 不同,应该使用StreamingContext.getOrCreate() 函数

def createStreamingContext() = {
...
val sc = new SparkContext(conf)
// 以1秒作为批次大小创建StreamingContext,此处你需要设置检查点目录
val ssc = new StreamingContext(sc, Seconds(1))
ssc.checkpoint(checkpointDir)
}
...
val ssc = StreamingContext.getOrCreate(checkpointDir, createStreamingContext _)
   当这段代码第一次运行时,假设检查点目录还不存在,那么 StreamingContext 会在你调用工厂函数(在 Scala 中为 createStreamingContext() ,在 Java 中为 JavaStreamingContextFactory() )时把目录创建出来。
在驱动器程序失败之后,如果你重启驱动器程序并再次执行代码, getOrCreate() 会重新从检查点目录中初始化出 StreamingContext,然后继续处理。除了用 getOrCreate() 来实现初始化代码以外,你还需要编写在驱动器程序崩溃时重启驱动器进程的代码。在大多数集群管理器中,Spark 不会在驱动器程序崩溃时自动重启驱动器进程,所以你需要使用诸如 monit 这样的工具来监视驱动器进程并进行重启。最佳的实现方式往往取决于你的具体环境。Spark 在独立集群管理器中提供了更丰富的支持,可以在提交驱动器程序时使用 --supervise 标记来让 Spark 重启失败的驱动器程序你还要传递 --deploy-mode cluster 参数来确保驱动器程序在集群中运行,而不是在本地机器上运行,
   3、工作节点容错
    所有从外部数据源中收到的数据都在多个工作节点上备份。所有从备份数据转化操作的过程中创建出来的 RDD 都能容忍一个工作节点的失败,因为根据 RDD 谱系图,系统可以把丢失的数据从幸存的输入数据备份中重算出来。
  4、接收器容错
    如果这样的节点发生错误,Spark Streaming会在集群中别的节点上重启失败的接收器。
41、SparkStreaming 读取外部数据源时,主动去数据源拉取数数据是可靠的。在“接收器从数据池中拉取数据”的模型中,Spark 只会在数据已经在集群中备份时才会从数据池中移除元素。而在“向接收器推数据”的模型中,如果接收器在数据备份之前失败,一些数据可能就会丢失。
42、 所有从可靠文件系统中读取的数据(比如通过 StreamingContext.hadoopFiles 读取的)都是可靠的,因为底层的文件系统是有备份的。Spark Streaming 会记住哪些数据存放到了检查点中,并在应用崩溃后从检查点处继续执行。
43、即使一个工作节点在处理部分数据时发生失败,最终的转化结果(即转化操作得到的 RDD)仍然与数据只被处理一次得到的结果一样。然而,当把转化操作得到的结果使用输出操作推入外部系统中时,写结果的任务可能因故障而执行多次,一些数据可能也就被写了多次。由于这引入了外部系统,因此我们需要专门针对各系统的代码来处理这样的情况。我们可以使用事务操作来写入外部系统(即原子化地将一个 RDD 分区一次写入),或者设计幂等的更新操作(即多次运行同一个更新操作仍生成相同的结果)。比如 Spark Streaming 的 saveAs...File 操作会在一个文件写完时自动将其原子化地移动到最终位置上,以此确保每个输出文件只存在一份。

         
 
展开阅读全文

没有更多推荐了,返回首页