Apache官方网站于2017年7月11日发布了Spark Release 2.2.0版本, Apache Spark 2.2.0版本是Spark 2.x系列上的第三个版本。Spark 2.2.0是Spark 2.x中第一个在生产环境可以使用的版本,对于Spark具有里程碑意义。Spark 2.2.0版本中 Structured Streaming 的实验性标记(experimental tag)已经被移除,此版本更多地侧重于系统的可用性(usability)、稳定性(stability)以及代码的polish,解决了1100个tickets。此外,只要安装pyspark,在Spark 2.2.0版本中PySpark可用于pypi。Spark 2.2.0版本移除了对 Java 7 以及 Hadoop 2.5及其之前版本的支持,移除了对Python 2.6的支持。
Apache Spark2.2.0版本的一些新功能:
· Core and Spark SQL
· Structured Streaming
· MLlib
· SparkR
· GraphX
· Deprecations
· Changes of behavior
· Known Issues
· Credits
如无特殊说明,本书所有内容都基于最新最稳定的Spark 2.2.0版本的源代码编写,为体现Spark源代码的演进过程,部分核心源代码在Spark 1.6.x、Spark 2.1.x源码的基础上,新增Spark 2.2.0版本的源代码,便于读者系统比对、研习Spark源码。
Spark 2.0中更新发布了新的流处理框架(Structured Streaming);对于API的更新,Spark 2.0版本API的更新主要包括DataFrame、DataSet、SparkSession、累加器API、Aggregator API等API的变动。
自从Spark得到广泛使用以来,其流处理框架Spark Streaming也逐渐地吸引到了很多用户,得益于其易用的高级API和一次性语义,使其成为使用最广泛的流处理框架之一。但是我们不仅仅需要流处理来构建实时应用程序,很多时候我们的应用程序只有一部分需要用到流处理,对于这种应用程序,Databricks公司把它称为ContinuousApplication(实时响应数据的端到端的应用程序),也就是连续的应用程序。在Continuous Application中会有许多难点,比如数据交互的完整性、流数据与离线数据的结合使用、在线机器学习等。
Spark2.0最重磅的更新是新的流处理框架——Structured Streaming。它允许用户使用DataFrame/DataSetAPI编写与离线批处理几乎相同的代码,便可以作用到流数据和静态数据上,引擎会自动增量化流数据计算,同时保证了数据处理的一致性,并且提供了和存储系统的事务集成。
在Spark2.0版本的API中共有如下几个API的变动:
1)统一了DataFrame和DataSet。现在DataFrame不再是一个独立的类,而是作为DataSet[Row]的别名定义在org.apache.spark.sql这个包对象中。
sql\package.scala源代码如下:
1. packageobject sql {
2.
3. /**
4. * Converts a logical plan into zero or moreSparkPlans. This API is exposed forexperimenting
5. * with the query planner and is not designedto be stable across spark releases. Developers
6. * writing libraries should instead considerusing the stable APIs provided in
7. * [[org.apache.spark.sql.sources]]
8. */
9. @DeveloperApi
10. @InterfaceStability.Unstable
11. type Strategy = SparkStrategy
12.
13. type DataFrame = Dataset[Row]
14. }
2)加入了SparkSession用于替换DataFrame和Dataset API的SQLContext和HiveContext(这两个API仍然可以使用)。
3)为SparkSession为SparkSQL加入一个新的,精简的配置参数–RuntimeConfig,可以用来设置和获得跟SparkSQL有关的Spark或者Hadoop设置。
SparkSession.scala源代码:
1. /**
2. * Runtime configuration interface for Spark.
3. *
4. * This is the interface through which theuser can get and set all Spark and Hadoop
5. * configurations that are relevant to SparkSQL. When getting the value of a config,
6. * this defaults to the value set in theunderlying `SparkContext`, if any.
7. *
8. * @since 2.0.0
9. */
10. @transient lazy val conf: RuntimeConfig = newRuntimeConfig(sessionState.conf)
4)更简单,更高性能的累加器API。
5)用于DataSet中类型化聚合的新的改进的AggregatorAPI。
本节讲解Tungsten引擎的新特性;SparkSession使用方法;以及一个更加简单和更高性能的累加器API的使用。
Spark备受瞩目的原因之一在于它的高性能,Spark开发者为了保持这个优势一直在不断的进行各种层次的优化,其中最令人兴奋的莫过于钨丝计划(ProjectTungsten),因为钨丝计划的提出给Spark带来了极大的性能提升,并且在一定程度上引导了Spark的发展方向。
Spark是使用Scala和Java语言开发的,不可避免的运行JVM之上,当然内存管理也是依赖于JVM的内存管理机制,而对于大数据量的基于内存的处理,JVM对象模型对内存的额外开销,以及频繁的GC和Full GC都是非常致命的问题。另外,随着网络带宽和磁盘IO的不断提升,内存和CPU又重新作为性能瓶颈受到关注,JVM对象的序列化、反序列化带来的性能损耗急需解决。Spark1.5版本加入的钨丝计划从3大方面着手解决这些问题:
1) 统一内存管理模型和二进制处理(BinaryProcessing)。统一内存管理模型来代替
之前基于JVM的静态内存管理,引入Page来管理堆内存和堆外内存(on-heap和off-heap),并且直接操作内存中的二进制数据而不是Java对象,很大程度上摆脱了JVM内存管理的限制。
2) 基于缓存感知的计算(Cache-aware Computation)。Spark内存读取操作也会带来
一部分性能损耗,钨丝计划便设计了缓存友好的算法和数据结构来提高缓存命中率,充分利用L1/L2/L3三级缓存,大幅提高了内存读取速度,进而缩短了内存中的整个计算过程的时间。
3) 代码生成(Code Generation)。在JVM中,所有代码的执行由解释器来一步步的
解释执行,CodeGeneration这一功能则在Spark运行时动态生成用于部分算子求值的bytecode,减少了对基础数据类型的封装,并且缓解了调用虚函数的额外开销。
Spark2.0升级了第二代Tungsten引擎。其中最重要的一点是把CodeGeneration作用于全阶段的SparkSQL和DataFrame之上,(即全阶段代码生成Whole Stage Code Generation),为常见的算子带来了十倍左右的性能提升!
加入SparkSession,取代原来的SQLContext和HiveContext,为了兼容两者仍然保留。SparkSession使用方法如下:
1. SparkSession.builder()
2. .master("local")
3. .appName("Word Count")
4. .config("spark.some.config.option","some-value")
5. .getOrCreate()
首先获得SparkSession的Builder,然后使用Builder来为SparkSession设置参数,最后使用getOrCreate方法来检测当前线程是否有一个已经存在的Thread-local级别的SparkSession,如果有则返回它,没有则检测是否有全局级别的SparkSession,有则返回没有则创建新的SparkSession。
在程序中如果要使用SparkContext时可以调用sparkSession.sparkContext即可。在程序的最后我们需要调用sparkContext.stop方法,这个方法会调用sparkContext.stop来关闭sparkContext。
从Spark2.0开始,DataFrame和DataSet既可以容纳静态、有限的数据,也可以容纳无限的流数据,所以用户也可以使用SparkSession像创建静态数据集一样来创建流式数据集,并且可以使用相同的操作算子。这样整合了实时流处理和离线处理的框架,结合其它容错、扩展等特性形成了完整的Lambda架构。
Spark2.0引入了一个更加简单和更高性能的累加器API,比如在1.X版本中可以这样使用累加器:
1. //定义累加器,这里直接使用SparkContext内置的累加器,设置初始值为0,名字为"My Accumulator"
2. val accum = sc.accumulator(0,"My Accumulator")
3. //计算值
4. sc.parallelize(Array(1, 2, 3,4)).foreach(x => accum += x)
5. //获取累加器的值,(Executor上面只能对累加器进行累加操作,只有Driver才能读取累加器的值,Driver读取值的时候会把各个Executor上存储的本地累加器的值加起来),这里结果是10。
6. accum.value
在2.X版本里使用SparkContext里内置的累加器:
1. //与1.X不同的是需要指定累加器的类型,目前SparkContext有Long类型和Double类型的累加器可以直接使用(不需要指定初始值)。
2. val accum =sc.longAccumulator("My Accumulator")
3. sc.parallelize(Array(1, 2, 3,4)).foreach(x => accum.add(x))
4. print(accum.value)
只使用SparkContext里内置的累加器功能肯定不能满足略微复杂的业务类型,此时我们就可以自定义累加器。在1.X版本的做法是(下面是官网的例子):
1. //继承AccumulatorParam[Vector],返回类型为Vector。
2. object VectorAccumulatorParamextends AccumulatorParam[Vector] {
3. //定义“零”值,这里把传入的初始值的size作为“零”值。
4. def zero(initialValue: Vector):Vector = {
5. Vector.zeros(initialValue.size)
6. }
7. //定义累加操作的计算方式
8. def addInPlace(v1: Vector, v2: Vector):Vector = {
9. v1 += v2
10. }
11. }
上面的累加器元素和返回类型是相同的,在Scala中还有另外一种方式来自定义累加器,用户只需要继承Accumulable,就可以把元素和返回值定义为不同的类型,这样我们就可以完成添加操作(比如像Int类型的List里添加整数,此时元素为Int类型,而返回类型为List)。
在Spark2.X中,加入了一个新的抽象类--AccumulatorV2,继承这个类要实现几个方法:
add方法:指定元素相加操作。
copy方法:指定对自定义的累加器的拷贝操作。
isZero方法:返回该累加器的值是否为“零”。
merge方法:合并两个相同类型的累加器。
reset方法:重置累加器。
value方法:返回累加器当前的值。
在重写这几个方法之后,只需实例化自定义累加器,并连同累加器名字一起传给sparkContext.register方法即可。
我们来简单实现一个把字符串合并为数组的累加器:
1. //首先要继承AccumulatorV2,并指定输入为String类型,输出为ArrayBuffer[String]
2. class MyAccumulator extendsAccumulatorV2[String, ArrayBuffer[String]] {
3. //设置累加器的结果,类型为ArrayBuffer[String]
4. private var result = ArrayBuffer[String]()
5.
6. //判断累加器当前值是否为“零值”,这里我们指定如果result的size为0则累加器的当前值是“零值”
7. override def isZero: Boolean =this.result.size == 0
8.
9. //copy方法设置为新建本累加器,并把result赋给新的累加器
10. override def copy(): AccumulatorV2[String,ArrayBuffer[String]] = {
11. val newAccum = new MyAccumulator
12. newAccum.result = this.result
13. newAccum
14. }
15. //reset方法设置为把result设置为新的ArrayBuffer
16. override def reset(): Unit =this.result == new ArrayBuffer[String]()
17.
18. //add方法是把传进来的字符串添加到result内
19. override def add(v: String):Unit = this.result += v
20.
21. //merge方法:把两个累加器的result合并起来
22. override def merge(other:AccumulatorV2[String, ArrayBuffer[String]]): Unit = {
23. result.++=:(other.value)
24. }
25. //value方法返回result
26. override def value: ArrayBuffer[String] =this.result
27. }
28. 接着在main方法里使用累加器:
29. val Myaccum = newMyAccumulator()
30.
31. //向SparkContext注册累加器
32. sc.register(Myaccum)
33.
34. //把“a”,“b”“c”“d”添加进累加器的result这个数组并打印出来
35. sc.parallelize(Array("a","b","c","d")).foreach(x=> Myaccum.add(x))
36. println(Myaccum.value)
运行结果显示的ArrayBuffer里的值顺序是不固定的,取决于各个Executor的值到达Driver的顺序。
Spark 2.0通过对SQL2003的支持增强了SQL功能,Catalyst新引擎提升了Spark查询优化的速度;本节对DataFrame和Dataset API、时间窗口进行了讲解。
Apache Spark2.2.0版本中核心和Spark SQL的更新:
1、 API更新
· SPARK-19107: Supportcreating hive table with DataFrameWriter and Catalog
· SPARK-13721: Add support forLATERAL VIEW OUTER explode()
· SPARK-18885: Unify CREATE TABLEsyntax for data source and hive serde tables
· SPARK-16475: Added BroadcastHints BROADCAST, BROADCASTJOIN, and MAPJOIN, for SQL Queries
· SPARK-18350: Support sessionlocal timezone
· SPARK-19261: Support ALTERTABLE table_name ADD COLUMNS
· SPARK-20420: Add events tothe external catalog
· SPARK-18127: Add hooks andextension points to Spark
· SPARK-20576: Support generichint function in Dataset/DataFrame
· SPARK-17203: Data sourceoptions should always be case insensitive
· SPARK-19139: AES-basedauthentication mechanism for Spark
2、 性能优化和系统稳定性
基于成本的优化:
· SPARK-17075 SPARK-17076SPARK-19020 SPARK-17077 SPARK-19350: Cardinality estimation for filter, join,aggregate, project and limit/sample operators
· SPARK-17080: Cost-based joinre-ordering
· SPARK-17626: TPC-DS performanceimprovements using star-schema heuristics
· SPARK-17949: Introduce a JVMobject based aggregate operator
· SPARK-18186: Partialaggregation support of HiveUDAFFunction
· SPARK-18362 SPARK-19918:File listing/IO improvements for CSV and JSON
· SPARK-18775: Limit the maxnumber of records written per file
· SPARK-18761: Uncancellable /unkillable tasks shouldn’t starve jobs of resources
· SPARK-15352: Topology awareblock replication
3、 其他的一些变化
· SPARK-18352: Support forparsing multi-line JSON files
· SPARK-19610: Support forparsing multi-line CSV files
· SPARK-21079: Analyze TableCommand on partitioned tables
· SPARK-18703: Drop StagingDirectories and Data Files after completion of Insertion/CTAS againstHive-serde Tables
· SPARK-18209: More robustview canonicalization without full SQL expansion
· SPARK-13446: [SPARK-18112]Support reading data from Hive metastore 2.0/2.1
· SPARK-18191: Port RDD API touse commit protocol
· SPARK-8425:Add blacklistmechanism for task scheduling
· SPARK-19464: Remove supportfor Hadoop 2.5 and earlier
· SPARK-19493: Remove Java 7support
Spark 2.0通过对SQL2003的支持大幅增强了SQL功能,现在可以运行所有99个TPC-DS查询。这个版本中的SparkSQL主要有以下几点改进:
1) 引入了支持ANSISQL和HiveSQL的本地解析器。
2) 本地实现DDL命令。
3) 支持非相关标量子查询。
4) 在Where与having条件中,支持(not)in和(not)exists。
5) 即使Spark没有和Hive集成搭建,SparkSQL也支持它们一起搭建时的除了Hive连接、Hive UDF(UserDefinedFunction用户自定义函数)和脚本转换之外的大部分功能。
6) Hive式的分桶方式的支持。
另外Catalyst查询优化器对于常见的工作负载也有了很多提升,对比如nullability propagation之类的查询做了更好的优化。Catalyst查询优化器从最早的应用于SparkSQL到现在应用于DataSetAPI,对Spark程序的高效率运行起到了非常重要的作用,并且随着DataSetAPI的流行,以及优化器自身的不断演进,未来肯定会对Spark的所有框架带来更高的执行效率。
在Spark 1.x版本中,DataFrame的API存在很多问题,比如说DataFrame不是类型安全的(nottype-safe)、不是面向对象的(notobject-oriented),为了克服这些问题,Spark在1.6版本引入了Dataset并在2.0版本的Scala和Java中将二者进行了统一(在Python和R中,由于缺少类型安全性,DataFrame仍是主要的编程接口),DataFrame成为了DataSet[Row]的别名,而且Spark2.0版本为DataSet的类型化聚合加入了一个新的聚合器,让基于DataSet的聚合更加高效。
在2.1版本中DataFrame和Dataset API晋升为稳定的API,也就是说可以在生产环境中使用它们,且后续会基于向后兼容的前提下不断强化。
DataSetAPI是High-LevelAPI,有更高的抽象级别,与RDDAPI这样的Low-LevelAPI相比更加易用,它对于提升用户的工作效率,以及提高程序的可读性而言意义非凡。由于WholeStageCodeGeneration的引入,SparkSQL和DataSetAPI中的常见算子的性能提升了2到10倍。加上Catalyst查询优化器和Tungsten的帮助,用户可以不用过多的关注对程序优化,也能获得很好的执行效率。
所以毋庸置疑地,这样一种简单高效的API将成为Spark未来主流的编程接口!
对于经常用到复杂SQL的用户而言,窗口函数一直以来都是不可或缺的,在Spark2.X版本中通过对Hive中的窗口函数的本地化实现,来使用spark的内存管理机制,从而提升了窗口函数的性能。
Spark2.0为我们带来了一个新的流处理框架Structured Streaming,这是一个基于Spark SQL和Catalyst优化器构建的高级流API。它允许用户使用与操作静态数据的DataFrame / Dataset API对流数据进行编程,利用Catalyst优化器自动地增量化查询计划。并且它不但支持流数据的不断写入,还支持其他的静态数据的插入。
Apache Spark2.2.0版本中Structured Streaming的更新:
1、 整体可用性:
· SPARK-20844: The StructuredStreaming APIs are now GA and is no longer labeled experimental
2、 Kafka 提升:
· SPARK-19719: Support forreading and writing data in streaming or batch to/from Apache Kafka
· SPARK-19968: Cached producerfor lower latency kafka to kafka streams.
3、 API 更新:
· SPARK-19067: Support forcomplex stateful processing and timeouts using [flat]MapGroupsWithState
· SPARK-19876: Support for onetime triggers
4、 其它的一些变化:
· SPARK-20979: Rate source fortesting and benchmarks