Spark中的闭包

闭包的作用可以理解为:函数可以访问函数外部定义的变量,但是函数内部对该变量进行的修改,在函数外是不可见的,即对函数外源变量不会产生影响。

其实,在学习Spark时,一个比较难理解的点就是,在集群模式下,定义的变量和方法作用域的范围和生命周期。这在你操作RDD时,比如调用一些函数map、foreach时,访问其外部变量进行操作时,很容易产生疑惑。为什么我本地程序运行良好且结果正确,放到集群上却得不到想要的结果呢?

首先通过下边对RDD中的元素进行求和的示例,来看相同的代码本地模式和集群模式运行结果的区别:

Spark为了执行任务,会将RDD的操作分解为多个task,并且这些task是由executor执行的。在执行之前,Spark会计算task的闭包即定义的一些变量和方法,比如例子中的counter变量和foreach方法,并且闭包必须对executor而言是可见的,这些闭包会被序列化发送到每个executor。

在集群模式下,driver和executor运行在不同的JVM进程中,发送给每个executor的闭包中的变量是driver端变量的副本。因此,当foreach函数内引用counter时,其实处理的只是driver端变量的副本,与driver端本身的counter无关。driver节点的内存中仍有一个计数器,但该变量对executor是不可见的!executor只能看到序列化闭包的副本。因此,上述例子输出的counter最终值仍然为零,因为counter上的所有操作都只是引用了序列化闭包内的值。

在本地模式下,往往driver和executor运行在同一JVM进程中。那么这些闭包将会被共享,executor操作的counter和driver持有的counter是同一个,那么counter在处理后最终值为6。

但是在生产中,我们的任务都是在集群模式下运行,如何能满足这种业务场景呢?

这就必须引出一个后续要重点讲解的概念:Accumulator即累加器。Spark中的累加器专门用于提供一种机制,用于在集群中的各个worker节点之间执行时安全地更新变量。

一般来说,closures - constructs比如循环或本地定义的方法,就不应该被用来改变一些全局状态,Spark并没有定义或保证对从闭包外引用的对象进行更新的行为。如果你这样操作只会导致一些代码在本地模式下能够达到预期的效果,但是在分布式环境下却事与愿违。如果需要某些全局聚合,请改用累加器。对于其他的业务场景,我们适时考虑引入外部存储系统、广播变量等。

闭包函数从产生到在executor执行经历了什么?

首先,对RDD相关的操作需要传入闭包函数,如果这个函数需要访问外部定义的变量,就需要满足一定条件(比如必须可被序列化),否则会抛出运行时异常。闭包函数在最终传入到executor执行,需要经历以下步骤:

1. driver通过反射,运行时找到闭包访问的变量,并封装成一个对象,然后序列化该对象

2. 将序列化后的对象通过网络传输到worker节点

3. worker节点反序列化闭包对象

4. worker节点的executor执行闭包函数

简而言之,就是要通过网络传递函数、然后执行,期间会经历序列化和反序列化,所以要求被传递的变量必须可以被序列化和反序列化,否则会抛类似Error:Task not serializable: java.io.NotSerializableException when calling function outside closure only on classes not objects这样的异常。即使是本地执行时,也会按照上述的步骤执行,这也是为什么不允许在RDD内部直接操作RDD的原因(SparkContext不支持序列化)。同时,在这些算子闭包内修改外部定义的变量不会被反馈到driver端。

driver & executor

driver是运行用户编写Application 的main()函数的地方,具体负责DAG的构建、任务的划分、task的生成与调度等。job,stage,task生成都离不开rdd自身,rdd的相关的操作不能缺少driver端的sparksession/sparkcontext。

executor是真正执行task地方,而task执行离不开具体的数据,这些task运行的结果可以是shuffle中间结果,也可以持久化到外部存储系统。一般都是将结果、状态等汇集到driver。但是,目前executor之间不能互相通信,只能借助第三方来实现数据的共享或者通信。

编写的Spark程序代码,运行在driver端还是executor端呢?

先看个简单例子:通常我们在本地测试程序的时候,要打印RDD中的数据。

在本地模式下,直接使用rdd.foreach(println)或rdd.map(println)在单台机器上,能够按照预期打印并输出所有RDD的元素。

但是,在集群模式下,由executor执行输出写入的是executor的stdout,而不是driver上的stdout,所以driver的stdout不会显示这些!

要想在driver端打印所有元素,可以使用collect()方法先将RDD数据带到driver节点,然后在调用foreach(println)(但需要注意一点,由于会把RDD中所有元素都加载到driver端,可能引起driver端内存不足导致OOM。如果你只是想获取RDD中的部分元素,可以考虑使用take或者top方法)

总之,在这里RDD中的元素即为具体的数据,对这些数据的操作都是由负责task执行的executor处理的,所以想在driver端输出这些数据就必须先将数据加载到driver端进行处理。

最后做个总结:所有对RDD具体数据的操作都是在executor上执行的,所有对rdd自身的操作都是在driver上执行的。比如foreach、foreachPartition都是针对rdd内部数据进行处理的,所以我们传递给这些算子的函数都是执行于executor端的。但是像foreachRDD、transform则是对RDD本身进行一列操作,所以它的参数函数是执行在driver端的,那么它内部是可以使用外部变量,比如在SparkStreaming程序中操作offset、动态更新广播变量等。


关注微信公众号:大数据学习与分享,获取更对技术干货

Spark闭包 | driver & executor程序代码执行的更多相关文章

  1. Apple macOS Mojave Intel Graphics Driver组件任意代码执行漏洞

    受影响系统:Apple macOS Mojave 10.14.5描述:CVE(CAN) ID: CVE-2019-8629 Apple macOS Mojave是苹果公司Mac电脑系列产品的操作系统. ...

  2. android 程序代码执行adb

    Runtime.getRuntime().exec("adb pull /dev/graphics/fb0 C:/fb1"); Runtime. getRuntime().exec ...

  3. [js]js代码执行顺序/全局&私有变量/作用域链/闭包

    js代码执行顺序/全局&私有变量/作用域链 <script> /* 浏览器提供全局作用域(js执行环境)(栈内存) --> 1,预解释(仅带var的可以): 声明+定义 1. ...

  4. day02编程语言,Python语言介绍,Python解释器安装,环境变量,Python代码执行,pip,应用程序使用文件的三步骤,变量,变量的三大组成,比较,pycharm

    复习 重点: 1.进制转换:二进制 与十六进制 2.内存分布:栈区 与堆区 # 二进制1111转换十六进制 => 8 4 2 1 => f 10101100111011 => 2a7 ...

  5. 使用Jacoco获取 Java 程序的代码执行覆盖率

    Jacoco是Java Code Coverage的缩写,顾名思义,它是获取Java代码执行覆盖率的一个工具,通常用它来获取单元测试覆盖率.它通过分析Java字节码来得到代码执行覆盖率,因此它还可以分 ...

  6. 编程语言分类,Python代码执行,应用程序使用文件的三步骤,变量,常量,垃圾回收机制

    编程语言分为 机器语言(直接用二进制01跟计算机直接沟通交流,直接操作硬件) 优点:计算机能够直接读懂,速度快 缺点:开发效率极低 汇编语言(用简单的英文标签来表示二进制数,直接操作硬件) 优点:开发 ...

  7. Delphi 如何在程序中执行动态生成的Delphi代码

    如何在程序中执行动态生成的Delphi代码 经常发现有人提这类问题,或者提问内容最后归结成这种问题 前些阵子有位高手写了一个“执行动态生成的代码”,这是真正的高手,我没那种功力,我只会投机取巧. 这里 ...

  8. 如何在程序中执行动态生成的Delphi代码

    如何在程序中执行动态生成的Delphi代码 经常发现有人提这类问题,或者提问内容最后归结成这种问题 前些阵子有位高手写了一个“执行动态生成的代码”,这是真正的高手,我没那种功力,我只会投机取巧. 这里 ...

  9. 【Spark深入学习 -14】Spark应用经验与程序调优

    ----本节内容------- 1.遗留问题解答 2.Spark调优初体验 2.1 利用WebUI分析程序瓶颈 2.2 设置合适的资源 2.3 调整任务的并发度 2.4 修改存储格式 3.Spark调 ...

随机推荐

  1. 测试之-Jmeter使用

    一. 修改语言 修改 在 bin 目录下的 Jemeter.properties 二 . Jmeter主要元件 1.测试计划:是使用 JMeter 进行测试的起点,它是其它 JMeter测试元件的容器 ...

  2. 2018HUAS_ACM暑假比赛5题解

    目录 Problem A Problem B Problem C Problem D Problem E Problem F Problem A 思路 这是一道带权并查集问题 因为只有三种种类,我们分 ...

  3. 函数-深入JS笔记

    代码特点:高内聚,低耦合 耦合 存在执行多个相同作用代码时,这就叫耦合 if (1 > 0) { console.log('a'); } if (2 > 0) { console.log( ...

  4. offer_JZ25

    offer_JZ25 题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点.(注意,输 ...

  5. PowerShell类grep

    PowerShell类grep 方法一: windows下没有grep不过有findstr, 功能差不多 方法二: powershell自带的正择功能 xxx | where {$_ -match & ...

  6. ES index not_analyzed

    在最初创建索引mapping时,未指定index:not_analyzed "exact_value": { "type": "string" ...

  7. MFiX-DEM中的串行碰撞搜索

    在计算颗粒碰撞的时候,需要进行neighbor颗粒的搜寻,只知道大概是基于网格与颗粒绑定的方式,但是具体的实现方式还是比较模糊.搜寻部分代码如下 (mfix-19.2.2): 可以直接观察到的是,这里 ...

  8. APP后台架构开发实践笔记

    1 App后台入门 1.1 App后台的功能 (1)远程存储数据: (2)消息中转. 1.2 App后台架构 架构设计的流程 (1) 根据App的设计,梳理出App的业务流程: (2) 把每个业务流程 ...

  9. SpringMVC找不到js等文件,有两种方式可以解决这个问题。

    (1)当你选择不过滤任何文件时,必须去springmvc.xml去设置默认加载. (2)如果你在web.xml中设置的过滤请求那么你就不用设置默认加载,但请求的url必须符合格式.

  10. ci框架根据配置自动生成controller控制器和model控制器(改版本)

    CI修改如下: if($modle_file=config_item('modle_file')) { if ($modle_file === TRUE) { $modle_file=config_i ...