├── _config.yml ├── imgs ├── mammut.png ├── spark_core │ ├── memory.png │ ├── rdd-itr.png │ ├── rdd-loop.png │ ├── shuffle.png │ ├── wc-trans.png │ ├── rdd-feature.png │ ├── spark-eco.png │ ├── wordcount.png │ ├── dependencies.png │ ├── rdd-inmemory.png │ ├── object-lifetime.png │ ├── repartition-less2more.png │ ├── repartition-more2less.png │ └── context_cleaner │ │ ├── jobs_tab_cached_rdd.png │ │ └── storage_tab_cached_rdd.jpg └── spark_basics │ ├── spark-stack.png │ └── sparkcontext-services.png ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .travis.yml ├── .gitignore ├── example ├── src │ └── main │ │ ├── scala │ │ └── com │ │ │ └── netease │ │ │ └── bigdata │ │ │ └── spark │ │ │ ├── WordCount.scala │ │ │ └── rdd │ │ │ └── RDDCacheTest.scala │ │ └── java │ │ └── com │ │ └── netease │ │ └── bigdata │ │ └── hadoop │ │ └── WordCount.java └── pom.xml ├── README.md ├── scalastyle-config.xml ├── pom.xml └── slides ├── spark_core ├── context_cleaner.html └── rdd_basics.html └── spark_basics └── spark_basics_and_quick_start.html /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-leap-day 2 | -------------------------------------------------------------------------------- /imgs/mammut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/mammut.png -------------------------------------------------------------------------------- /imgs/spark_core/memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/memory.png -------------------------------------------------------------------------------- /imgs/spark_core/rdd-itr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/rdd-itr.png -------------------------------------------------------------------------------- /imgs/spark_core/rdd-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/rdd-loop.png -------------------------------------------------------------------------------- /imgs/spark_core/shuffle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/shuffle.png -------------------------------------------------------------------------------- /imgs/spark_core/wc-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/wc-trans.png -------------------------------------------------------------------------------- /imgs/spark_core/rdd-feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/rdd-feature.png -------------------------------------------------------------------------------- /imgs/spark_core/spark-eco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/spark-eco.png -------------------------------------------------------------------------------- /imgs/spark_core/wordcount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/wordcount.png -------------------------------------------------------------------------------- /imgs/spark_basics/spark-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_basics/spark-stack.png -------------------------------------------------------------------------------- /imgs/spark_core/dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/dependencies.png -------------------------------------------------------------------------------- /imgs/spark_core/rdd-inmemory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/rdd-inmemory.png -------------------------------------------------------------------------------- /imgs/spark_core/object-lifetime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/object-lifetime.png -------------------------------------------------------------------------------- /imgs/spark_core/repartition-less2more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/repartition-less2more.png -------------------------------------------------------------------------------- /imgs/spark_core/repartition-more2less.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/repartition-more2less.png -------------------------------------------------------------------------------- /imgs/spark_basics/sparkcontext-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_basics/sparkcontext-services.png -------------------------------------------------------------------------------- /imgs/spark_core/context_cleaner/jobs_tab_cached_rdd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/context_cleaner/jobs_tab_cached_rdd.png -------------------------------------------------------------------------------- /imgs/spark_core/context_cleaner/storage_tab_cached_rdd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-bigdata/ne-spark-courseware/HEAD/imgs/spark_core/context_cleaner/storage_tab_cached_rdd.jpg -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **Expected behavior** 11 | A clear and concise description of what you expected to happen. 12 | 13 | **Screenshots** 14 | If applicable, add screenshots to help explain your problem. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | ## 题名 8 | 【在这里描述你的主题】 9 | ## 摘要 10 | 【概要性描述你的主题内容】 11 | ## 大纲 12 | - 自我介绍 13 | - 目录 14 | - 主题一 15 | - 主题一-1 16 | - 主题一-2 17 | - 主题一-3 18 | - 主题二 19 | - 主题二-1 20 | - 主题二-2 21 | - 主题三 22 | - 主题三-1 23 | - 结尾 24 | 25 | ## 附录 26 | 【在这里描述你的主题涵盖的其他信息】 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.8 4 | 5 | cache: 6 | directories: 7 | - $HOME/.m2 8 | 9 | deploy: 10 | provider: pages 11 | skip_cleanup: true 12 | github_token: $GITHUB_TOKEN 13 | email: yaooqinn@hotmail.com 14 | name: Kent Yao 15 | on: 16 | branch: master 17 | 18 | script: 19 | - mvn package -q -Dmaven.javadoc.skip=true -B -V 20 | 21 | notifications: 22 | email: false 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *#*# 2 | *.#* 3 | *.iml 4 | *.ipr 5 | *.iws 6 | *.pyc 7 | *.pyo 8 | *.swp 9 | *~ 10 | .DS_Store 11 | .cache 12 | .classpath 13 | .ensime 14 | .ensime_cache/ 15 | .ensime_lucene 16 | .generated-mima* 17 | .idea/ 18 | .idea_modules/ 19 | .project 20 | .pydevproject 21 | .scala_dependencies 22 | .settings 23 | target/ 24 | dist/ 25 | kyuubi-*-bin-* 26 | *.gz 27 | logs/ 28 | pid/ 29 | local/ 30 | out/ 31 | hs_err_pid* 32 | spark-warehouse/ 33 | metastore_db 34 | derby.log 35 | 36 | -------------------------------------------------------------------------------- /example/src/main/scala/com/netease/bigdata/spark/WordCount.scala: -------------------------------------------------------------------------------- 1 | package com.netease.bigdata.spark 2 | 3 | import org.apache.spark.{SparkConf, SparkContext} 4 | 5 | object WordCount { 6 | 7 | def main(args: Array[String]): Unit = { 8 | require(args.length == 1, "Usage: WordCount ") 9 | val conf = new SparkConf().setAppName("Word Count").setMaster("local[*]") 10 | val sparkContext = new SparkContext(conf) 11 | val textFile = sparkContext.textFile(args(0), 2) 12 | val words = textFile.flatMap(_.split(" ")) 13 | val ones = words.map((_, 1)) 14 | val counts = ones.reduceByKey(_ + _) 15 | val res = counts.collect() 16 | for ((word, count) <- res) { 17 | println(word + ": " + count) 18 | } 19 | 20 | sparkContext.stop() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /example/src/main/scala/com/netease/bigdata/spark/rdd/RDDCacheTest.scala: -------------------------------------------------------------------------------- 1 | package com.netease.bigdata.spark.rdd 2 | 3 | import org.apache.spark.{SparkConf, SparkContext} 4 | 5 | import scala.util.Random 6 | 7 | object RDDCacheTest { 8 | 9 | def main(args: Array[String]): Unit = { 10 | val conf = new SparkConf() 11 | .setAppName(getClass.getSimpleName) 12 | .set("spark.cleaner.periodicGC.interval", "1min") // context cleaner 13 | val sc = new SparkContext(conf) 14 | val data = Seq.fill(1024 * 1024 * 100)(Random.nextInt(100)) 15 | val rdd1 = sc.parallelize(data, 20) 16 | rdd1.cache() // mark rdd 1 cache 17 | val rdd2 = rdd1.map((_, 1)).reduceByKey(_ + _) // word count 18 | val cachedRdd2 = rdd2.cache() // cache shuffled rdd 19 | rdd2.collect() // action actually trigger caching 20 | rdd1.count() // ditto 21 | rdd2.count() // rdd reuse 22 | cachedRdd2.count() // ditto 23 | rdd1.map((_, 1)).reduceByKey(_ + _).take(1) // rdd 1 reuse, not rdd 2 24 | // no rdd reuse 25 | val rdd3 = sc.parallelize(data, 30) 26 | rdd3.map((_, 1)).reduceByKey(_ + _).count() 27 | 10.to(20, 2).foreach { i => 28 | val tmp = rdd3.groupBy(_ % i) 29 | tmp.cache().count() 30 | if (i % 3 == 0) tmp.take(1) 31 | } 32 | Thread.sleep(1000 * 60 * 10) 33 | sc.stop() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/src/main/java/com/netease/bigdata/hadoop/WordCount.java: -------------------------------------------------------------------------------- 1 | package com.netease.bigdata.hadoop; 2 | 3 | import org.apache.hadoop.conf.Configuration; 4 | import org.apache.hadoop.fs.Path; 5 | import org.apache.hadoop.io.IntWritable; 6 | import org.apache.hadoop.io.LongWritable; 7 | import org.apache.hadoop.io.Text; 8 | import org.apache.hadoop.mapreduce.Job; 9 | import org.apache.hadoop.mapreduce.Mapper; 10 | import org.apache.hadoop.mapreduce.Reducer; 11 | import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; 12 | import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; 13 | import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; 14 | import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; 15 | 16 | import java.io.IOException; 17 | import java.util.StringTokenizer; 18 | 19 | public class WordCount { 20 | 21 | public static class Map extends Mapper { 22 | private final static IntWritable one = new IntWritable(1); 23 | private Text word = new Text(); 24 | 25 | public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { 26 | String line = value.toString(); 27 | StringTokenizer tokenizer = new StringTokenizer(line); 28 | while (tokenizer.hasMoreTokens()) { 29 | word.set(tokenizer.nextToken()); 30 | context.write(word, one); 31 | } 32 | } 33 | } 34 | 35 | public static class Reduce extends Reducer { 36 | public void reduce(Text key, Iterable values, Context context) 37 | throws IOException, InterruptedException { 38 | int sum = 0; 39 | for (IntWritable val : values) { 40 | sum += val.get(); 41 | } 42 | context.write(key, new IntWritable(sum)); 43 | } 44 | } 45 | 46 | public static void main(String[] args) throws Exception { 47 | Configuration conf = new Configuration(); 48 | Job job = new Job(conf, "wordcount"); 49 | job.setOutputKeyClass(Text.class); 50 | job.setOutputValueClass(IntWritable.class); 51 | job.setMapperClass(Map.class); 52 | job.setReducerClass(Reduce.class); 53 | job.setInputFormatClass(TextInputFormat.class); 54 | job.setOutputFormatClass(TextOutputFormat.class); 55 | FileInputFormat.addInputPath(job, new Path(args[0])); 56 | FileOutputFormat.setOutputPath(job, new Path(args[1])); 57 | job.waitForCompletion(true); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetEase Spark Courses [](http://hits.dwyl.io/netease-bigdata/ne-spark-courseware) 2 | 3 | 本项目旨在指导相关的用户在使用[网易猛犸大数据平台](https://bigdata.163yun.com/mammut)的过程中能够更加方便使用Apache Spark进行日常的数据开发工作。 4 | 5 | 6 | ## 一、基础知识 7 | #### 1. [Spark概述及快速入门指南](https://netease-bigdata.github.io/ne-spark-courseware/slides/spark_basics/spark_basics_and_quick_start.html#1) 8 | #### 2. [基于Maven在IDE中开发Spark应用]() 9 | 10 | ## 二、 Spark Core 11 | #### 1. [Spark RDD概述](https://netease-bigdata.github.io/ne-spark-courseware/slides/spark_core/rdd_basics.html#1) 12 | #### 2. [Spark垃圾回收机制 -- ContextCleaner](https://netease-bigdata.github.io/ne-spark-courseware/slides/spark_core/context_cleaner.html#1) 13 | #### [Spark On YARN]() 14 | 15 | ## 三、 Spark SQL 16 | #### [DataFrame/Dataset]() 17 | #### [Spark SQL与Hive集成]() 18 | #### [Spark SQL UDF]() 19 | #### [如何优化Spark SQL执行过程]() 20 | #### [Spark SQL Catalyst工作原理详解]() 21 | #### [Spark SQL Cost Based Optimization详解]() 22 | #### [Spark SQL Thrift Server详解]() 23 | #### [Spark SQL 操作各种数据源]() 24 | #### [Spark SQL 参数详解及调优]() 25 | 26 | ## 四、 Spark Streaming 27 | #### [大数据处理的类型、流计算的框架及内容概要]() 28 | #### [SparkStreaming是什么及数据处理流程]() 29 | #### [Spark Streaming集成Kafka]() 30 | #### [Spark Streaming集成Flume]() 31 | 32 | 33 | ## 五、 Spark Structured Streaming 34 | #### [Spark Structured Streaming Basics](https://yaooqinn.github.io/sugar/slides/StructuedStreamingBasics.html#1) 35 | 36 | ## 六、 Spark Machine Learning 37 | 38 | ## 七、 Spark GraphX 39 | 40 | ## 八、 R on Spark 41 | 42 | ## 九、 Mammut Spark 数据开发 43 | #### [如何使用猛犸Spark进行数据开发]() 44 | #### [如何使用猛犸进行ETL开发]() 45 | #### [如何使用猛犸Spark Streaming任务开发及调优]() 46 | 47 | ## 十、 Mammut Spark 自助分析 48 | 49 | ## 十一、 Spark 参数详解 50 | 51 | ## 十二、 其他 52 | - DataSourceV2 53 | - [DataSourceV2 Overview](https://yaooqinn.github.io/sugar/docs/spark/datasourcev2/1_start_from_the_jira.html) - 范文臣大神[SPIP: DataSource API V2](https://docs.google.com/document/d/1n_vUVbF4KD3gxTmkNEon5qdQ-Z8qU5Frf6WMQZ6jJVM/edit#heading=h.mi1fbff5f8f9)读后感 54 | 55 | --- 56 | 57 | ## 推广链接 58 | [Kyuubi](https://github.com/yaooqinn/kyuubi) 基于Spark实现的多租户SQL Thrift/JDBC/ODBC服务 [](https://codecov.io/gh/yaooqinn/kyuubi) [](https://travis-ci.org/yaooqinn/kyuubi)[](http://hits.dwyl.io/yaooqinn/kyuubi) 59 | 60 | [spark-authorizer](https://github.com/yaooqinn/spark-authorizer) 提供Spark SQL权限控制能力的插件 [](https://travis-ci.org/yaooqinn/spark-authorizer) 61 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spark-courseware 7 | com.netease.bigdata 8 | 1.0.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | example 14 | Examples 15 | jar 16 | 17 | 18 | 19 | org.scala-lang 20 | scala-library 21 | 22 | 23 | org.apache.hadoop 24 | hadoop-mapreduce-client-core 25 | 26 | 27 | org.apache.hadoop 28 | hadoop-client 29 | 30 | 31 | 32 | org.apache.spark 33 | spark-core_${scala.binary.version} 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 3.5.1 43 | 44 | ${java.version} 45 | ${java.version} 46 | UTF-8 47 | 1024m 48 | true 49 | 50 | -Xlint:all,-serial,-path 51 | 52 | 53 | 54 | 55 | 56 | net.alchim31.maven 57 | scala-maven-plugin 58 | 3.3.1 59 | 60 | 61 | eclipse-add-source 62 | 63 | add-source 64 | 65 | 66 | 67 | scala-compile-first 68 | 69 | compile 70 | 71 | 72 | 73 | scala-test-compile-first 74 | 75 | testCompile 76 | 77 | 78 | 79 | 80 | ${scala.version} 81 | incremental 82 | true 83 | 84 | -unchecked 85 | -deprecation 86 | -feature 87 | -explaintypes 88 | -Yno-adapted-args 89 | 90 | 91 | -Xms1024m 92 | -Xmx1024m 93 | -XX:ReservedCodeCacheSize=512M 94 | 95 | 96 | -source 97 | ${java.version} 98 | -target 99 | ${java.version} 100 | -Xlint:all,-serial,-path,-try 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /scalastyle-config.xml: -------------------------------------------------------------------------------- 1 | 17 | 39 | 40 | 41 | Scalastyle standard configuration 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | true 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ARROW, EQUALS, ELSE, TRY, CATCH, FINALLY, LARROW, RARROW 105 | 106 | 107 | 108 | 109 | 110 | ARROW, EQUALS, COMMA, COLON, IF, ELSE, DO, WHILE, FOR, MATCH, TRY, CATCH, FINALLY, LARROW, RARROW 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Runtime\.getRuntime\.addShutdownHook 119 | 127 | 128 | 129 | 130 | mutable\.SynchronizedBuffer 131 | 139 | 140 | 141 | 142 | Await\.ready 143 | 150 | 151 | 152 | 153 | JavaConversions 154 | Instead of importing implicits in scala.collection.JavaConversions._, import 155 | scala.collection.JavaConverters._ and use .asScala / .asJava methods 156 | 157 | 158 | 159 | org\.apache\.commons\.lang\. 160 | Use Commons Lang 3 classes (package org.apache.commons.lang3.*) instead 161 | of Commons Lang 2 (package org.apache.commons.lang.*) 162 | 163 | 164 | 165 | extractOpt 166 | Use Utils.jsonOption(x).map(.extract[T]) instead of .extractOpt[T], as the latter 167 | is slower. 168 | 169 | 170 | 171 | 172 | java,scala,3rdParty,yaooqinn 173 | javax?\..* 174 | scala\..* 175 | (?!yaooqinn).* 176 | 177 | 178 | 179 | 180 | 181 | COMMA 182 | 183 | 184 | 185 | 186 | \)\{ 187 | 190 | 191 | 192 | 193 | (?m)^(\s*)/[*][*].*$(\r|)\n^\1 [*] 194 | Use Javadoc style indentation for multiline comments 195 | 196 | 197 | 198 | case[^\n>]*=>\s*\{ 199 | Omit braces in case clauses. 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 800> 250 | 251 | 252 | 253 | 254 | 30 255 | 256 | 257 | 258 | 259 | 10 260 | 261 | 262 | 263 | 264 | 50 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | -1,0,1,2,3 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 4.0.0 22 | 23 | com.netease.bigdata 24 | spark-courseware 25 | Spark Courseware 26 | 1.0.0-SNAPSHOT 27 | 28 | example 29 | 30 | pom 31 | 32 | 33 | 34 | The Apache Software License, Version 2.0 35 | http://www.apache.org/licenses/LICENSE-2.0.txt 36 | manual 37 | 38 | 39 | 40 | 41 | 42 | yaooqinn 43 | Kent Yao 44 | yaooqinn@hotmail.com 45 | NetEase 46 | https://github.com/yaooqinn 47 | 48 | 49 | 50 | 51 | UTF-8 52 | 1.7 53 | 2.11.8 54 | 2.2.6 55 | 2.11 56 | 3.3.9 57 | org.apache.spark 58 | 2.1.2 59 | provided 60 | 2.6.5 61 | provided 62 | org.spark-project.hive 63 | 1.2.1.spark2 64 | 1.2.1 65 | provided 66 | 1.1 67 | 2.0.0-M15 68 | 69 | 70 | 71 | 72 | central 73 | 74 | Maven Repository 75 | https://repo.maven.apache.org/maven2 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | 84 | apache 85 | Apache Repository Snapshots 86 | http://repository.apache.org/snapshots 87 | 88 | false 89 | 90 | 91 | true 92 | daily 93 | warn 94 | 95 | 96 | 97 | 98 | 99 | 100 | central 101 | https://repo.maven.apache.org/maven2 102 | 103 | true 104 | 105 | 106 | false 107 | 108 | 109 | 110 | apache 111 | Apache Repository Snapshots 112 | http://repository.apache.org/snapshots 113 | 114 | false 115 | 116 | 117 | true 118 | daily 119 | warn 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | ${spark.group} 128 | spark-yarn_${scala.binary.version} 129 | ${spark.version} 130 | ${spark.scope} 131 | 132 | 133 | org.apache.hadoop 134 | * 135 | 136 | 137 | 138 | 139 | ${spark.group} 140 | spark-hive_${scala.binary.version} 141 | ${spark.version} 142 | ${spark.scope} 143 | 144 | 145 | 146 | ${spark.group} 147 | spark-tags_${scala.binary.version} 148 | ${spark.version} 149 | ${spark.scope} 150 | 151 | 152 | 153 | org.scala-lang 154 | scala-library 155 | ${scala.version} 156 | provided 157 | 158 | 159 | 160 | org.apache.hadoop 161 | hadoop-mapreduce-client-core 162 | ${hadoop.version} 163 | ${hadoop.deps.scope} 164 | 165 | 166 | 167 | org.apache.hadoop 168 | hadoop-client 169 | ${hadoop.version} 170 | ${hadoop.deps.scope} 171 | 172 | 173 | asm 174 | asm 175 | 176 | 177 | org.codehaus.jackson 178 | jackson-mapper-asl 179 | 180 | 181 | org.ow2.asm 182 | asm 183 | 184 | 185 | org.jboss.netty 186 | netty 187 | 188 | 189 | commons-logging 190 | commons-logging 191 | 192 | 193 | org.mockito 194 | mockito-all 195 | 196 | 197 | org.mortbay.jetty 198 | servlet-api-2.5 199 | 200 | 201 | javax.servlet 202 | servlet-api 203 | 204 | 205 | junit 206 | junit 207 | 208 | 209 | com.sun.jersey 210 | * 211 | 212 | 213 | com.sun.jersey.jersey-test-framework 214 | * 215 | 216 | 217 | com.sun.jersey.contribs 218 | * 219 | 220 | 221 | 222 | 223 | 224 | org.eclipse.jetty 225 | jetty-servlet 226 | 9.3.11.v20160721 227 | 228 | 229 | 230 | com.google.guava 231 | guava 232 | 14.0.1 233 | provided 234 | 235 | 236 | 237 | net.sf.jpam 238 | jpam 239 | ${jpam.version} 240 | provided 241 | 242 | 243 | 244 | org.apache.hadoop 245 | hadoop-yarn-client 246 | ${hadoop.version} 247 | ${hadoop.deps.scope} 248 | 249 | 250 | 251 | org.scalatest 252 | scalatest_${scala.binary.version} 253 | ${scalatest.version} 254 | test 255 | 256 | 257 | 258 | ${spark.group} 259 | spark-core_${scala.binary.version} 260 | ${spark.version} 261 | 262 | 263 | 264 | ${spark.group} 265 | spark-catalyst_${scala.binary.version} 266 | ${spark.version} 267 | test-jar 268 | test 269 | 270 | 271 | ${spark.group} 272 | spark-sql_${scala.binary.version} 273 | ${spark.version} 274 | test-jar 275 | test 276 | 277 | 278 | ${hive.group} 279 | hive-service 280 | ${hive.version} 281 | test 282 | 283 | 284 | 285 | org.apache.hadoop 286 | hadoop-minikdc 287 | ${hadoop.version} 288 | test 289 | 290 | 291 | org.apache.directory.api 292 | api-all 293 | 294 | 295 | org.apache.directory.jdbm 296 | apacheds-jdbm1 297 | 298 | 299 | 300 | 301 | org.apache.directory.server 302 | apacheds-service 303 | ${apacheds.version} 304 | test 305 | 306 | 307 | bouncycastle 308 | bcprov-jdk15 309 | 310 | 311 | 312 | 313 | org.apache.curator 314 | curator-test 315 | 2.6.0 316 | test 317 | 318 | 319 | org.mockito 320 | mockito-core 321 | 1.10.19 322 | test 323 | 324 | 325 | 326 | 327 | 328 | 329 | spark-2.1 330 | 331 | 2.1.2 332 | 333 | 334 | 335 | 336 | spark-2.2 337 | 338 | 2.2.1 339 | 340 | 341 | 342 | 343 | spark-2.3 344 | 345 | 2.3.0 346 | 3.0.3 347 | 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /slides/spark_core/context_cleaner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache Spark Garbge Collector 4 | 5 | 145 | 146 | 147 | 148 | 149 | class: center, middle, inverse 150 | name: 标题页 151 | 152 | ## [NetEase Spark Courses](https://netease-bigdata.github.io/ne-spark-courseware/) 153 | 154 | 155 | 156 | 157 | 158 | 159 | ## Apache Spark Garbge Collector --- ContextCleaner 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | ??? 169 | 备注:标题 170 | 帮助信息:在网页端按H键可进入帮助页面 171 | 172 | --- 173 | 174 | class: inverse, center 175 | name: agenda 176 | 177 | # Agenda 178 | ## - 179 | ** About Me ** 180 | ## - 181 | 182 | ** 什么是 ContextCleaner? ** 183 | 184 | ** ContextCleaner 工作原理 ** 185 | 186 | ** 什么时候需要关心 ContextCleaner? ** 187 | 188 | ** 如何配置 ContextCleaner? ** 189 | 190 | ??? 191 | 192 | 备注: 目录 193 | 194 | 本 TOPIC 内容包括Spark程序内部垃圾回收器(ContextCleaner)的相关介绍 195 | 196 | --- 197 | 198 | class: inverse 199 | name: aboutme 200 | 201 | .left-column-inverse[ 202 | # About Me 203 | ] 204 | 205 | .right-column-inverse[ 206 | ## Kent Yao 207 | 208 | 2016年11月加入网易,目前在杭州研究院-数据科学中心担任资深大数据平台开发工程师,主导 Spark 作为核心计算框架在[网易大数据平台](https://bigdata.163yun.com/mammut)的相关研发及大规模应用工作。 209 | 210 | 前华为技术有限公司大数据技术开发部成员。 211 | 212 | 213 | 214 | GitHub: https://github.com/yaooqinn 215 | 216 | 217 | 218 | 219 | 220 | 221 | ] 222 | 223 | ??? 224 | 备注:个人简介 225 | 226 | --- 227 | 228 | class: inverse 229 | name: why 230 | 231 | .left-column-inverse[ 232 | ## Why? 233 | ] 234 | 235 | .right-column-inverse[ 236 | 237 | 238 | 239 | #### 缓存的 RDD 为啥重算了? 240 | 241 | #### Shuffle 残留, Executor内存吃紧, 乃至磁盘被打爆? 242 | 243 | #### 长时应用 Spark Streaming 间歇性停顿 244 | 245 | #### 长稳服务 Thrift Server 响应缓慢 246 | 247 | #### 一些Driver端的谜之停顿,甚至Hang住 248 | 249 | #### 不小心 -XX:+DisableExplicitGC 250 | 251 | ] 252 | 253 | ??? 254 | 255 | Spark 作为一个JVM based分布式计算框架,我我们基于Spark编写的应用程序,也会遇到 256 | 257 | --- 258 | 259 | class: inverse 260 | name: context cleaner overview 261 | 262 | .left-column-inverse[ 263 | ## What? 264 | ### Overview 265 | #### ContextCleaner 266 | ] 267 | 268 | .right-column-inverse[ 269 | 270 | #### 什么是 ContextCleaner? 271 | 272 | ##### **Driver 端**异步清理线程 273 | - 清理缓存过,但不再引用的 RDD 274 | - 清理该 RDD 对应的 ShuffleDependency 数据 275 | - Driver 端 Shuffle 元数据 276 | - Executor端 Shuffle 文件 277 | - 伴生的Broadcast 元数据变量 278 | - 以及 Broadcast、累加器变量、检查点数据 279 | 280 | ##### 通过Java WeakReference机制来接受**垃圾回收器**通知进行变量的清理 281 | 282 | #### 什么是 CleanupTask? 283 | ```scala 284 | private sealed trait CleanupTask /** 各种内部变量的清理任务 */ 285 | private case class CleanRDD(rddId: Int) extends CleanupTask 286 | private case class CleanShuffle(shuffleId: Int) extends CleanupTask 287 | private case class CleanBroadcast(broadcastId: Long) extends CleanupTask 288 | private case class CleanAccum(accId: Long) extends CleanupTask 289 | private case class CleanCheckpoint(rddId: Int) extends CleanupTask 290 | ``` 291 | ] 292 | 293 | --- 294 | 295 | class: inverse 296 | name: context cleaner overview 297 | 298 | .left-column-inverse[ 299 | ## What? 300 | ### Overview 301 | #### ContextCleaner 302 | #### CleanupTaskWeakRef... 303 | ] 304 | 305 | .right-column-inverse[ 306 | 什么是 CleanupTaskWeakReference? 307 | 308 | ```scala 309 | /** 310 | * 封装 CleanupTask 的 WeakReference. 当 referent 指向的 object 变成弱引用, 311 | * 其被自动加入 ReferenceQueue. 312 | */ 313 | private class CleanupTaskWeakReference( 314 | val task: CleanupTask, 315 | referent: AnyRef, 316 | referenceQueue: ReferenceQueue[AnyRef]) 317 | extends WeakReference(referent, referenceQueue) 318 | ``` 319 | 320 | 1.注册 321 | 322 | ```scala 323 | /** Register an object for cleanup. */ 324 | private def registerForCleanup(objectForCleanup: AnyRef, task: CleanupTask): Unit = { 325 | referenceBuffer.add(new CleanupTaskWeakReference(task, objectForCleanup, referenceQueue)) 326 | } 327 | 328 | ``` 329 | 330 | 2.可达性分析 referent 可达性变化 331 | 332 | 3.垃圾回收器将已注册的引用对象添加到 referenceQueue 中 333 | 334 | 4.我们拿到这个队列进行遍历,然后做些对应的清理工作 - CleanupTask 335 | ] 336 | 337 | ??? 338 | 339 | --- 340 | 341 | class: inverse 342 | name: context cleaner conponents 343 | 344 | .left-column-inverse[ 345 | ## What? 346 | ### Overview 347 | ### Conponents 348 | ] 349 | 350 | .right-column-inverse[ 351 | 352 | #### referenceBuffer 353 | ``` 354 | /** 355 | * A buffer to ensure that `CleanupTaskWeakReference`s are not garbage collected 356 | * as long as they have not been handled by the reference queue. 357 | */ 358 | private val referenceBuffer = 359 | Collections.newSetFromMap[CleanupTaskWeakReference](new ConcurrentHashMap) 360 | ``` 361 | 362 | 363 | - 确保每个 CleanupTaskWeakReference 在被 ReferenceQueue 处理之前,自身不被回收掉 364 | - 从而确保 RDD/Shuffle/Broadcast等对象被正确的清理 365 | - 一旦开始清理, 就会把对 CleanupTaskWeakReference 引用移除 366 | 367 | ] 368 | 369 | --- 370 | 371 | class: inverse 372 | name:context cleaner conponents 373 | 374 | .left-column-inverse[ 375 | ## What? 376 | ### Overview 377 | ### Conponents 378 | ] 379 | 380 | .right-column-inverse[ 381 | 382 | #### referenceQueue 383 | ``` 384 | private val referenceQueue = new ReferenceQueue[AnyRef] 385 | 386 | ``` 387 | 388 | - ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中 389 | 390 | - 用于 CleanupTaskWeakReference 的构建, 通过这个队列我们可以在某个对象即将回收时,搞一些“事情” 391 | 392 | - ContextCleaner 通过这个队列遍历出那些 CleanupTaskWeakReference 引用, 进而进行清理 393 | 394 | ] 395 | --- 396 | 397 | class: inverse 398 | name: context cleaner conponents 399 | 400 | .left-column-inverse[ 401 | ## What? 402 | ### Overview 403 | ### Conponents 404 | ] 405 | 406 | .right-column-inverse[ 407 | 408 | #### cleaningThread 409 | ``` 410 | private val cleaningThread = new Thread() { override def run() { keepCleaning() }} 411 | 412 | ``` 413 | ```scala 414 | /** Keep cleaning RDD, shuffle, and broadcast state. */ 415 | private def keepCleaning(): Unit = Utils.tryOrStopSparkContext(sc) { 416 | while (!stopped) { 417 | try { 418 | val reference = Option(referenceQueue.remove(ContextCleaner.REF_QUEUE_POLL_TIMEOUT)) 419 | .map(_.asInstanceOf[CleanupTaskWeakReference]) 420 | synchronized { 421 | reference.foreach { ref => 422 | referenceBuffer.remove(ref) 423 | ref.task match { 424 | case CleanRDD(rddId) => 425 | doCleanupRDD(rddId, blocking = blockOnCleanupTasks) 426 | ... 427 | case CleanBroadcast(broadcastId) => 428 | doCleanupBroadcast(broadcastId, blocking = blockOnCleanupTasks) 429 | ... 430 | } 431 | } 432 | } 433 | } 434 | ... 435 | } 436 | 437 | ``` 438 | 循环获取referenceQueue中的弱引用,根据 CleanupTask 进行对应清理 439 | ] 440 | 441 | --- 442 | 443 | class: inverse 444 | name: context cleaner conponents 445 | 446 | .left-column-inverse[ 447 | ## What? 448 | ### Overview 449 | ### Conponents 450 | ] 451 | 452 | .right-column-inverse[ 453 | 454 | #### periodicGCService 455 | ``` 456 | private val periodicGCService: ScheduledExecutorService = 457 | ThreadUtils.newDaemonSingleThreadScheduledExecutor("context-cleaner-periodic-gc") 458 | ``` 459 | 460 | ```scala 461 | /** Start the cleaner. */ 462 | def start(): Unit = { 463 | ... 464 | periodicGCService.scheduleAtFixedRate(new Runnable { 465 | override def run(): Unit = System.gc() 466 | }, periodicGCInterval, periodicGCInterval, TimeUnit.SECONDS) 467 | } 468 | 469 | ``` 470 | ReferenceQueue 与 Spark 程序 Driver 端的 GC 有关 471 | 472 | - 常规的GC 473 | - 自己调用System.gc 474 | - Spark 周期性的 System.gc 475 | ] 476 | 477 | --- 478 | 479 | class: inverse 480 | name: context cleaner conponents 481 | 482 | .left-column-inverse[ 483 | ## How? 484 | ### 栗子 485 | ] 486 | .right-column-inverse[ 487 | 488 | ```scala 489 | object RDDCacheTest { 490 | def main(args: Array[String]): Unit = { 491 | val conf = new SparkConf() 492 | .setAppName(getClass.getSimpleName) 493 | .set("spark.cleaner.periodicGC.interval", "1min") // context cleaner 494 | val sc = new SparkContext(conf) 495 | val data = Seq.fill(1024 * 1024 * 100)(Random.nextInt(100)) 496 | val rdd1 = sc.parallelize(data, 20) 497 | rdd1.cache() // mark rdd 1 cache 498 | val rdd2 = rdd1.map((_, 1)).reduceByKey(_ + _) // word count 499 | val cachedRdd2 = rdd2.cache() // cache shuffled rdd 500 | rdd2.collect() // action actually trigger caching 501 | rdd1.count() // ditto 502 | rdd2.count() // rdd reuse 503 | cachedRdd2.count() // ditto 504 | rdd1.map((_, 1)).reduceByKey(_ + _).take(1) // rdd 1 reuse, not rdd 2 505 | val rdd3 = sc.parallelize(data, 30) 506 | rdd3.map((_, 1)).reduceByKey(_ + _).count() // no rdd reuse 507 | 10.to(20, 2).foreach { i => 508 | val tmp = rdd3.groupBy(_ % i) 509 | tmp.cache().count() 510 | if (i % 3 == 0) tmp.take(1) 511 | } 512 | Thread.sleep(1000 * 60 * 10) 513 | sc.stop() 514 | } 515 | } 516 | ``` 517 | 518 | 上述例子中,我们简单的调用了RDD.cache, 引入Shuffle算子,再基于Spark自己对broadcast变量的利用,看下ContextCleaner的工作原理 519 | ] 520 | 521 | --- 522 | 523 | class: inverse 524 | name: context cleaner conponents 525 | 526 | .left-column-inverse[ 527 | ## How? 528 | ### 栗子 529 | ### 初始化 530 | ] 531 | .right-column-inverse[ 532 | 533 | ``` 534 | val sc = new SparkContext(conf) 535 | ``` 536 | ⬇️ 537 | ```scala 538 | _cleaner = 539 | if (_conf.getBoolean("spark.cleaner.referenceTracking", true)) { 540 | Some(new ContextCleaner(this)) 541 | } else { 542 | None 543 | } 544 | _cleaner.foreach(_.start()) 545 | 546 | ``` 547 | 548 | 在默认情况下(spark.cleaner.referenceTracking=true),SparkContext实例化的时候为我们自动初始化ContextCleaner 549 | ] 550 | 551 | --- 552 | 553 | class: inverse 554 | name: context cleaner conponents 555 | 556 | .left-column-inverse[ 557 | ## How? 558 | ### 栗子 559 | ### 初始化 560 | ### cache 561 | ] 562 | .right-column-inverse[ 563 | 564 | ``` 565 | rdd1.cache() 566 | ``` 567 | ⬇️ 568 | ```scala 569 | /** 570 | * Mark this RDD for persisting using the specified level. 571 | * 572 | * @param newLevel the target storage level 573 | * @param allowOverride whether to override any existing level with the new one 574 | */ 575 | private def persist(newLevel: StorageLevel, allowOverride: Boolean): this.type = { 576 | // TODO: Handle changes of StorageLevel 577 | if (storageLevel != StorageLevel.NONE && newLevel != storageLevel && !allowOverride) { 578 | throw new UnsupportedOperationException( 579 | "Cannot change storage level of an RDD after it was already assigned a level") 580 | } 581 | // If this is the first time this RDD is marked for persisting, register it 582 | // with the SparkContext for cleanups and accounting. Do this only once. 583 | if (storageLevel == StorageLevel.NONE) { 584 | sc.cleaner.foreach(_.registerRDDForCleanup(this)) 585 | sc.persistRDD(this) 586 | } 587 | storageLevel = newLevel 588 | this 589 | } 590 | 591 | ``` 592 | 593 | 在默认情况下(spark.cleaner.referenceTracking=true),调用RDD.cache会将自身注册到ContextCleaner 594 | ] 595 | 596 | --- 597 | class: inverse 598 | name: context cleaner conponents 599 | 600 | .left-column-inverse[ 601 | ## How? 602 | ### 栗子 603 | ### 初始化 604 | ### cache 605 | ### shuffle 606 | ] 607 | .right-column-inverse[ 608 | 609 | ``` 610 | val rdd2 = rdd1.map((_, 1)).reduceByKey(_ + _) 611 | ``` 612 | ⬇️ 613 | ```scala 614 | 615 | class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag]( 616 | @transient private val _rdd: ...) 617 | extends Dependency[Product2[K, V]] { 618 | ... 619 | 620 | val shuffleId: Int = _rdd.context.newShuffleId() 621 | 622 | val shuffleHandle: ShuffleHandle = _rdd.context.env.shuffleManager.registerShuffle( 623 | shuffleId, _rdd.partitions.length, this) 624 | 625 | _rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this)) 626 | } 627 | 628 | ``` 629 | 630 | 在默认情况下(spark.cleaner.referenceTracking=true),调用Shuffle过程中,会将自身注册到ContextCleaner 631 | 632 | PS: 之前提到了broadcast等 注册也都是大同小异的逻辑 633 | 634 | ] 635 | 636 | --- 637 | class: inverse 638 | name: context cleaner conponents 639 | 640 | .left-column-inverse[ 641 | ## How? 642 | ### 栗子 643 | ### 初始化 644 | ### cache 645 | ### shuffle 646 | ### Job 647 | ] 648 | .right-column-inverse[ 649 | 650 | 651 | 652 | RDD的Cache可以大大的帮我提升程序的性能,滥用的话也可能影响性能 653 | 654 | 1. 过多cache,挤占计算资源 655 | 2. 清理时的压力 656 | 657 | ] 658 | 659 | --- 660 | class: inverse 661 | name: context cleaner conponents 662 | 663 | .left-column-inverse[ 664 | ## How? 665 | ### 栗子 666 | ### 初始化 667 | ### cache 668 | ### shuffle 669 | ### Job 670 | ### Storage 671 | ] 672 | .right-column-inverse[ 673 | 674 | ``` 675 | Thread.sleep(1000 * 60 * 10) 676 | ``` 677 | ⬇️ 678 | 679 | 680 | 681 | 主线程休眠的10分钟内: 682 | 1. for循环内的缓存的RDD都将变得可达性都会变化,都被我们的ContextCleaner回收 683 | 2. 它们对应的Shuffle缓存信息、文件(如果有)都会相应得到清理 684 | 3. 循环之外的两个RDD,一直存活 685 | 4. 它们对应的Shuffle缓存信息、文件(如果有)都将一直存在 686 | ] 687 | 688 | --- 689 | class: inverse 690 | name: context cleaner conponents 691 | 692 | .left-column-inverse[ 693 | ## How? 694 | ### 栗子 695 | ### 初始化 696 | ### cache 697 | ### shuffle 698 | ### Job 699 | ### Storage 700 | ### Configuration 701 | ] 702 | .right-column-inverse[ 703 | ```scala 704 | spark.cleaner.referenceTracking 705 | 总开关 706 | 默认值:true 707 | 708 | spark.cleaner.referenceTracking.cleanCheckpoints 709 | 是否开启检查点数据的清理 710 | 默认值:false 711 | 712 | spark.cleaner.periodicGC.interval 713 | System.gc() 执行周期; 714 | 调节这个参数,一方面可以让 Driver 端的元数据(MapStatus)等,回收的更加顺滑 715 | 另一方面,可以及时触发 Executor 端 shuffle数据的及时清理 716 | 默认值:30min 717 | 718 | spark.cleaner.referenceTracking.blocking 719 | 标记清理线程是否在清理除Shuffle数据之外时阻塞 720 | 默认值:true 721 | 722 | spark.cleaner.referenceTracking.blocking.shuffle 723 | 标记清理线程在清理shuffle数据时是否阻塞 724 | 默认值:false 725 | ``` 726 | 727 | 阻塞: 清理线程需要等待所有 Executor 执行的结果 728 | 729 | 不阻塞: Driver 先狂发消息给 Executor 端, 接着就要被回来的消息淹没 730 | ] 731 | 732 | --- 733 | 734 | class: inverse 735 | name: sub-agenda 736 | 737 | .left-column-inverse[ 738 | ## 推荐 739 | ] 740 | 741 | .right-column-inverse[ 742 | 743 | #### Case Study: 744 | 745 | - [Debugging a long-running Apache Spark application: A War Story](https://tech.channable.com/posts/2018-04-10-debugging-a-long-running-apache-spark-application.html#footnote1) 746 | 747 | #### Spark Issues: 748 | - [SPARK-3015](https://issues.apache.org/jira/browse/SPARK-3015) - Removing broadcast in quick successions causes Akka timeout - 关于为什么清理时要blocking 749 | - [SPARK-3139](https://issues.apache.org/jira/browse/SPARK-3139) - Akka timeouts from ContextCleaner when cleaning shuffles - 关于为什么清理shuffle时为何不blocking 750 | - [SPARK-1855](https://issues.apache.org/jira/browse/SPARK-1855)- Provide memory-and-local-disk RDD checkpointing - 关于为何不默认注册检查点数据的清理 751 | 752 | ] 753 | 754 | --- 755 | 756 | class: middle, center, inverse 757 | name: greetings 758 | # Q & A 759 | 760 | --- 761 | 762 | class: middle, center, inverse 763 | name: greetings 764 | # Thank You! 765 | ### [Kent Yao] 766 | 767 | 768 | 769 | This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. 770 | 771 | 772 | 773 | 775 | 790 | 791 | 792 | -------------------------------------------------------------------------------- /slides/spark_basics/spark_basics_and_quick_start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Spark概述及快速入门指南 4 | 5 | 145 | 146 | 147 | 148 | 149 | class: center, middle, inverse 150 | name: 标题页 151 | 152 | ## [NetEase Spark Courses](https://netease-bigdata.github.io/ne-spark-courseware/) 153 | 154 | 155 | 156 | 157 | 158 | 159 | ## Apache Spark 概述及快速入门指南 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | ??? 169 | 备注:标题 170 | 帮助信息:在网页端按H键可进入帮助页面 171 | 172 | --- 173 | 174 | class: inverse, center 175 | name: agenda 176 | 177 | # Agenda 178 | ## - 179 | ** About Me ** 180 | ## - 181 | 182 | ** 什么是 Spark? ** 183 | 184 | ** 正确理解 Spark 相关概念 ** 185 | 186 | ** 怎么用 Spark? ** 187 | 188 | ** 如何提升 Spark 技能? ** 189 | 190 | ??? 191 | 192 | 备注: 目录 193 | 194 | 本 TOPIC 从最基本的方面讲解 Spark 入门所需具备的入手手段 195 | 196 | 内容尽可能的涵盖: 197 | 大数据处理的基本知识 198 | 从数据开发工程师或者数据分析师角度需要理解的 Spark 的一些基本概念 199 | 如何和底层大数据工程师(hadoop/spark 等)及运维合理的交流问题 200 | 以及如何在 Spark 这条路上一条道走到黑,走的更远 201 | 202 | --- 203 | 204 | class: inverse 205 | name: aboutme 206 | 207 | .left-column-inverse[ 208 | # About Me 209 | ] 210 | 211 | .right-column-inverse[ 212 | ## Kent Yao 213 | 214 | 2016年11月加入网易,目前在杭州研究院-数据科学中心担任资深大数据平台开发工程师,主导 Spark 作为核心计算框架在[网易大数据平台](https://bigdata.163yun.com/mammut)的相关研发及大规模应用工作。 215 | 216 | 前华为技术有限公司大数据技术开发部成员。 217 | 218 | 219 | 220 | GitHub: https://github.com/yaooqinn 221 | 222 | 223 | 224 | 225 | 226 | 227 | ] 228 | 229 | ??? 230 | 备注:个人简介 231 | 232 | --- 233 | 234 | class: inverse 235 | name: sub-agenda 236 | 237 | .left-column-inverse[ 238 | # Agenda 239 | ## 什么是 Spark? 240 | ] 241 | 242 | .right-column-inverse[ 243 | ### 什么是 Spark? 244 | ### Spark v.s. Hadoop 245 | ### Spark v.s. Hive 246 | ### Spark v.s. Impala 247 | ### Spark v.s. Flink 248 | ] 249 | 250 | ??? 251 | 备注:子目录 252 | 253 | 本章主要从 Spark 本身及Hadoop生态中的各类优秀组件的比较重,对于 Spark 的能力,定位,场景有较为客观的认识 254 | 255 | 由于能力、篇幅有限,只能基于当下的场景给出较为片面的个人见解 256 | 257 | --- 258 | 259 | class: inverse 260 | name: whatisrddoverview 261 | 262 | .left-column-inverse[ 263 | ## 什么是 Spark? 264 | ### 从定义上看 265 | ] 266 | 267 | .right-column[ 268 | 269 | Apache Spark™ is a unified analytics engine for large-scale data processing. 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | Apache® Spark™ Ecosystem 283 | 284 | ] 285 | 286 | ??? 287 | 备注:从定义上理解什么是 Spark 288 | 从官方目前给出的定义,Apache Spark 是一台支持大规模数据处理标准统一的分析引擎 289 | 290 | 模块涵盖了 291 | Spark SQL - 离线结构化数据的处理方案 292 | Spark Streaming - 流式计算框架 293 | Spark MLlib - 机器学习框架 294 | Graphx - 图计算框架 295 | 296 | 同时这些框架都运行在通用的 Spark Core 底层计算框架上面 297 | 298 | 得益于 2.x 版本后 API 层面在 SparkSession/DataFrame 上的高度统一,使得用户可以基于 “one stack to rule them all” 来大大滴爽一把。 299 | 300 | --- 301 | class: inverse 302 | name: sparkvshadoop 303 | 304 | .left-column-inverse[ 305 | ## 什么是 Spark? 306 | ### 从定义上看 307 | ### 从特点上看 308 | ] 309 | 310 | .right-column-inverse[ 311 | 312 | #### 速度块 313 | - DAG - 使用 DAG 对 RDD 的关系进行建模,描述其依赖关系 314 | - [Catalyst](https://databricks.com/blog/2015/04/13/deep-dive-into-spark-sqls-catalyst-optimizer.html) - 优化器框架 315 | - [Project Tungsten](https://databricks.com/blog/2015/04/28/project-tungsten-bringing-spark-closer-to-bare-metal.html) - 内存管理、二进制处理、缓存友好、Code-Gen 316 | - [CBO](https://databricks.com/blog/2017/08/31/cost-based-optimizer-in-apache-spark-2-2.html) - 基于代价的优化器 317 | - [Continuous Processing](https://databricks.com/blog/2018/03/20/low-latency-continuous-processing-mode-in-structured-streaming-in-apache-spark-2-3-0.html) - Streaming实现准实时到实时 318 | - ... 319 | 320 | #### 易用性 321 | - 丰富的高阶 API 算子 322 | - 丰富的语言支持:Java, Scala, Python, R, SQL 323 | 324 | #### 通用性 - 统一的 API 和 统一的底层模型: One Stack to Rule Them All 325 | 326 | #### 多平台 - Standalone/YARN/Mesos/K8S; DataSources 327 | 328 | ] 329 | 330 | ??? 331 | 332 | 备注:从 Spark 特点上理解 333 | 334 | 在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无环图( DAG 图) 335 | Spark SQL Paper: http://people.csail.mit.edu/matei/papers/2015/sigmod_spark_sql.pdf 336 | 337 | RDD Paper: https://www2.eecs.berkeley.edu/Pubs/TechRpts/2011/EECS-2011-82.pdf 338 | 339 | --- 340 | 341 | class: inverse 342 | name: sparkvshadoop 343 | 344 | .left-column-inverse[ 345 | ## Spark v.s. Hadoop 346 | ] 347 | 348 | .right-column-inverse[ 349 | 350 | Apache Hadoop™ 大数据基础生态"事实标准" 351 | 352 | #### HDFS 353 | - 大数据存储"事实标准" - 高可靠的分布式文件系统,可作为Spark等计算引擎的可靠底层存储承载 354 | 355 | #### YARN 356 | - 大数据资源调度"事实标准" - 允许我们各种大数据应用(包括Spark)以多租户的模式共享集群 357 | 358 | #### MapReduce 359 | - 低阶"呆板"的计算框架 - Spark等等诸多大数据计算框架所challenge的点 360 | 361 | ] 362 | 363 | ??? 364 | 备注: spark v.s. hadoop 365 | 366 | --- 367 | 368 | class: inverse 369 | name: sparkvsmapreduce 370 | 371 | .left-column-inverse[ 372 | ## Spark v.s. Hadoop 373 | ### Spark v.s MapReduce 374 | ] 375 | 376 | .right-column-inverse[ 377 | 378 | Apache Spark™ 大数据计算框架"事实标准" 379 | 380 | 381 | #### MapReduce缺点 382 | 383 | - 抽象层次低,难以上手 384 | - 表达能力欠佳,局限map/reduce算子,Job的lineage管理 385 | - 大量shuffle,大量落盘,不适合迭代 386 | 387 | 388 | 事实上就像你要装修房子的时候,不小心选了“清工”,你得告诉泥工干啥,木工干啥,X工干啥,然后在告诉包工头什么时候泥工需要做完啥,木工需要做完啥…… 最后发现实际上就像你用双手装修完了房子 389 | 390 | 391 | #### Spark的优点 392 | - RDD抽象 - 弹性分布式数据集在单机上的完美表达 393 | - 丰富的算子 - 像乐高积木一样灵活,易玩 394 | - 高效的迭代算法 395 | 396 | 397 | 终于把事情交给了一个靠谱的包工头 398 | 399 | ] 400 | 401 | ??? 402 | 备注: spark v.s. mapreduce 403 | 404 | 一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合, 405 | 每个RDD可以分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算。RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集来创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和groupBy)而创建得到新的RDD。 406 | 407 | --- 408 | 409 | class:inverse 410 | name: hadoopwordcount 411 | 412 | .left-column-inverse[ 413 | ## Spark v.s. Hadoop 414 | ### Spark v.s MapReduce 415 | ### Hadoop WordCount 416 | ] 417 | 418 | .right-column-inverse[ 419 | ```java 420 | public class WordCount { 421 | public static class Map extends Mapper { 422 | private final static IntWritable one = new IntWritable(1); 423 | private Text word = new Text(); 424 | public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { 425 | String line = value.toString(); 426 | StringTokenizer tokenizer = new StringTokenizer(line); 427 | while (tokenizer.hasMoreTokens()) { 428 | word.set(tokenizer.nextToken()); 429 | context.write(word, one); 430 | } 431 | } 432 | } 433 | public static class Reduce extends Reducer { 434 | public void reduce(Text key, Iterable values, Context context) 435 | throws IOException, InterruptedException { 436 | int sum = 0; 437 | for (IntWritable val : values) { 438 | sum += val.get(); 439 | } 440 | context.write(key, new IntWritable(sum)); 441 | } 442 | } 443 | public static void main(String[] args) throws Exception { 444 | Configuration conf = new Configuration(); 445 | Job job = new Job(conf, "wordcount"); 446 | job.setOutputKeyClass(Text.class); 447 | job.setOutputValueClass(IntWritable.class); 448 | job.setMapperClass(Map.class); 449 | job.setReducerClass(Reduce.class); 450 | job.setInputFormatClass(TextInputFormat.class); 451 | job.setOutputFormatClass(TextOutputFormat.class); 452 | FileInputFormat.addInputPath(job, new Path(args[0])); 453 | FileOutputFormat.setOutputPath(job, new Path(args[1])); 454 | job.waitForCompletion(true); 455 | } 456 | } 457 | ``` 458 | ] 459 | 460 | --- 461 | 462 | class: inverse 463 | name: sparkwordcount 464 | 465 | .left-column-inverse[ 466 | ## Spark v.s. Hadoop 467 | ### Spark v.s MapReduce 468 | ### Hadoop WordCount 469 | ### Spark WordCount 470 | ] 471 | 472 | .right-column-inverse[ 473 | 474 | ```scala 475 | object WordCount { 476 | 477 | def main(args: Array[String]): Unit = { 478 | val conf = new SparkConf().setAppName("Word Count").setMaster("local[*]") 479 | val sparkContext = new SparkContext(conf) 480 | val textFile = sparkContext.textFile(args(0), 2) 481 | val words = textFile.flatMap(_.split(" ")) 482 | val ones = words.map((_, 1)) 483 | val counts = ones.reduceByKey(_ + _) 484 | val res = counts.collect() 485 | for ((word, count) <- res) { 486 | println(word + ": " + count) 487 | } 488 | sparkContext.stop() 489 | } 490 | } 491 | ``` 492 | 493 | 或者一句话搞定: 494 | ```scala 495 | sc.textFile("README.md").flatMap(_.split("\\s+")).map((_, 1)).reduceByKey(_ + _).collect() 496 | ``` 497 | ] 498 | 499 | --- 500 | 501 | class: inverse 502 | name: sparkvshive 503 | 504 | .left-column-inverse[ 505 | ## Spark v.s. Hive 506 | ] 507 | 508 | .right-column-inverse[ 509 | 510 | Apache Hive MetaStore 大数据元数据"事实标准" 511 | 512 | 513 | #### Hive 514 | - "原始"的SQL on Hadoop方案 515 | - 实用、使用成本低,往往在中大型的互联网企业中积累了大量的业务和用户 516 | - 查询性能通常很低 517 | 518 | #### Spark SQL 519 | - 基于Spark Core的SQL on Hadoop方案 520 | - 查询性能很快 521 | - 和Hive的关系可以用"暧昧"来形容 522 | 523 | #### Kyuubi 524 | - 基于Spark SQL提供企业级SQL on Hadoop解决方案 525 | - https://github.com/yaooqinn/kyuubi 526 | 527 | ] 528 | 529 | --- 530 | 531 | class: inverse 532 | name: sparkvsimpala 533 | 534 | .left-column-inverse[ 535 | ## Spark v.s. Impala 536 | ] 537 | 538 | .right-column-inverse[ 539 | 540 | #### Impala 541 | - MPP 架构 - 分治思想、均分 task,share nothing、Scalability问题 542 | - 查询性能最快 543 | - Workload 建议在百亿级别之下 544 | - Impala on YARN 已被放弃,与现有计算集群无法融合 545 | 546 | #### Spark SQL 547 | - DAG 架构 - MR模型、Shared storage、 全局meta,优秀的Scalability 548 | - 查询性能也快 549 | - Workload > 百亿级别 550 | - Spark on YARN 可无缝融入现有计算资源池 551 | 552 | ] 553 | 554 | --- 555 | 556 | class: inverse 557 | name: sparkvsflink 558 | 559 | .left-column-inverse[ 560 | ## Spark v.s. Flink 561 | ] 562 | 563 | .right-column-inverse[ 564 | 565 | - Flink作为Spark的有力挑战者,正在以新的计算模型尝试解决Spark也在尝试解决的问题 566 | 567 | - 两者相互"学习"、"借鉴" 568 | - Spark Streaming (Maintained) - DStream、RDD based、微批处理、高吞吐、准实时 569 | - Spark Structrured Streaming - DataFrame Based、 微批处理/Continuous Process、高吞吐、准实时/实时 570 | - Flink - 数据流及事件序列模型、流执行模式、实时 571 | 572 | ] 573 | 574 | --- 575 | 576 | class: inverse 577 | name: sub-agenda 578 | 579 | .left-column-inverse[ 580 | # Agenda 581 | ## Spark Glossary 582 | ] 583 | 584 | .right-column-inverse[ 585 | ### SparkContext 586 | ### Application 587 | ### Configuration 588 | ### Deployment 589 | ### Monitoring 590 | ### Tuning 591 | ] 592 | 593 | ??? 594 | 595 | 备注:理解 Spark 相关的术语及其背后包含的语义, 596 | 597 | 有助于用户正确理解 Spark 的构成 598 | 599 | 有助于用户正确和底层平台开发和维护人员进行有效的沟通 600 | 601 | --- 602 | 603 | class: inverse 604 | name:sparkcontext 605 | 606 | .left-column-inverse[ 607 | ## Spark Glossary 608 | ### SparkContext 609 | ] 610 | .right-column-inverse[ 611 | - the entrance / the heart / the master of a Spark APP 612 | 613 | 614 | ] 615 | 616 | --- 617 | 618 | class: inverse 619 | name: application 620 | 621 | .left-column-inverse[ 622 | ## Spark Glossary 623 | ### SparkContext 624 | ### Application 625 | ] 626 | 627 | .right-column-inverse[ 628 | 629 | 一个Spark应用是一个由包含 SparkContext 实例主程序作为 Driver 进程,并协调一堆独立的 Executor 进程构成的一个 Master/Salve 的结构 630 | 631 | 632 | ```scala 633 | Driver Program - 包含 SparkContext 对象的用户主程序, 调度节点 634 | Executor - task处理节点、计算节点 635 | Cluster Manager - 管理集群计算资源的外部服务,常见的有 Standalone Mesos, k8s, YARN 636 | 637 | ``` 638 | 639 | ] 640 | 641 | ??? 642 | Spark运行架构包括集群资源管理器(Cluster Manager)、运行作业任务的工作节点(Worker Node)、每个应用的任务控制节点(Driver)和每个工作节点上负责具体任务的执行进程(Executor)。其中,集群资源管理器可以是Spark自带的资源管理器,也可以是YARN或Mesos等资源管理框架。 643 | 与Hadoop MapReduce计算框架相比,Spark所采用的Executor有两个优点:一是利用多线程来执行具体的任务(Hadoop MapReduce采用的是进程模型),减少任务的启动开销;二是Executor中有一个BlockManager存储模块,会将内存和磁盘共同作为存储设备,当需要多轮迭代计算时,可以将中间结果存储到这个存储模块里,下次需要时,就可以直接读该存储模块里的数据,而不需要读写到HDFS等文件系统里,因而有效减少了IO开销;或者在交互式查询场景下,预先将表缓存到该存储系统上,从而可以提高读写IO性能。 644 | 645 | --- 646 | 647 | class: inverse 648 | name: applicationcomponents 649 | 650 | .left-column-inverse[ 651 | ## Spark Glossary 652 | ### SparkContext 653 | ### Application 654 | ] 655 | 656 | .right-column-inverse[ 657 | Application 658 | ``` 659 | - 用户主程序 660 | - 包含主程序依赖、Spark jars, 可包含多个Job 661 | ``` 662 | RDD 663 | ``` 664 | - 用户编程模型,只读的、分布式的数据集及包含的运算的非分布概念抽象 665 | - transformation - 指定RDD之间的相互依赖关系 666 | - action - 用户执行计算,指定输出形式 667 | ``` 668 | Job 669 | ``` 670 | - 以 action 算子为划分 671 | ``` 672 | Stage 673 | ``` 674 | - 以 shuffle 算子划分,也即下游的算子有可能需要从上有算子全量的全量输出中获得输入 675 | ``` 676 | Task 677 | ``` 678 | - 对应 RDD 分区数,将一个 stage 划分成一堆 task,由 Driver 调度到 Executor 计算 679 | ``` 680 | 681 | ] 682 | 683 | ??? 684 | 685 | 在Spark中,一个应用(Application)由一个任务控制节点(Driver)和若干个作业(Job)构成,一个作业由多个阶段(Stage)构成,一个阶段由多个任务(Task)组成。当执行一个应用时,任务控制节点会向集群管理器(Cluster Manager)申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在Executor上执行任务,运行结束后,执行结果会返回给任务控制节点,或者写到HDFS或者其他数据库中。 686 | 687 | Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分阶段,具体划分方法是:在DAG中进行反向解析,遇到宽依赖就断开,遇到窄依赖就把当前的RDD加入到当前的阶段中;将窄依赖尽量划分在同一个阶段中,可以实现流水线计算 688 | 689 | 两类操作的主要区别是,transformations(比如map、filter、groupBy、join等)接受RDD并返回RDD, 690 | 而action操作(比如count、collect等)接受RDD但是返回非RDD(即输出一个值或结果)。 691 | RDD提供的转换接口都非常简单,都是类似map、filter、groupBy、join等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改。 692 | 693 | --- 694 | 695 | class: inverse 696 | name: configuration 697 | 698 | .left-column-inverse[ 699 | ## Spark Glossary 700 | ### SparkContext 701 | ### Application 702 | ### Configuration 703 | ] 704 | 705 | .right-column-inverse[ 706 | 707 | Spark Properties 708 | ```scala 709 | # 配置文件 710 | conf/spark-defaults.conf 711 | 712 | # 硬编码 713 | val conf = new SparkConf().setMaster("local[2]").setAppName("NetEase") 714 | 715 | # 动态传参 716 | bin/spark-submit \ 717 | --name "Netease" \ 718 | --master local[4] \ 719 | --conf spark.eventLog.enabled=false myapp.jar 720 | 721 | ``` 722 | 723 | 724 | 725 | 更多可参照文档: https://spark.apache.org/docs/latest/configuration.html#spark-configuration 726 | 727 | ] 728 | 729 | --- 730 | 731 | class: inverse 732 | name: deployment 733 | 734 | .left-column-inverse[ 735 | ## Spark Glossary 736 | ### SparkContext 737 | ### Application 738 | ### Configuration 739 | ### Deployment 740 | ] 741 | 742 | .right-column-inverse[ 743 | 744 | Cluster Manager 745 | ``` 746 | - spark.master / --master 747 | - Standalone - 在集群上启动对应的Master 和 Worker进程,作为资源的管理器 748 | - Local - 单进程充当Driver和Executor 749 | - Mesos - Apache下的开源分布式资源管理框架 750 | - YARN - 生产环境首选资源管理器,支持Hive、Spark、Flink各种任务的资源调度 751 | - k8s - 暂时还是实验特性 752 | ``` 753 | Deploy Mode 754 | ``` 755 | - spark.submit.deplyMode / --deploy-mode 756 | - client - Driver 运行在本地,ApplicationMaster 只作为 Executor Launcher 757 | - 该模式一般用于调试场景,可以方便自己查看 Driver 端日志,定位问题 758 | - 单机的负载极限 759 | - Driver 潜在的网络瓶颈 760 | 761 | - cluster - Driver 运行在 ApplicationMaster 内 762 | - 该模式一般用于生产环境 763 | - YARN ApplicationMaster带 failover 可防止一些潜在问题 764 | - 同一集群内 Driver 所处网络环境较好 765 | - 同一NodeManager节点过多 Container 而产生的本地资源竞争 766 | - spark.driver.cores 767 | - spark.driver.memory 768 | ``` 769 | 770 | ] 771 | 772 | --- 773 | 774 | class: inverse 775 | name: monitoring 776 | 777 | .left-column-inverse[ 778 | ## Spark Glossary 779 | ### SparkContext 780 | ### Application 781 | ### Deployment 782 | ### Configuration 783 | ### Monitoring 784 | ] 785 | 786 | .right-column-inverse[ 787 | 788 | Live UI 789 | 790 | ``` 791 | http://:4040 792 | ``` 793 | 794 | History Server 795 | 796 | ``` 797 | http://:18080 798 | ``` 799 | Metrics 800 | 801 | ``` 802 | $SPARK_HOME/conf/metrics.properties 803 | 804 | ``` 805 | Log 806 | ``` 807 | # 配置 808 | $SPARK_HOME/conf/log4j.properties 809 | ``` 810 | ``` 811 | # 一般定位顺序 812 | Driver -> ApplicationMaster -> Executor -> NodeManager 813 | ``` 814 | ``` 815 | # 命令 816 | yarn logs -applicationId [app_id] -appOwner [user_name] 817 | ``` 818 | ] 819 | 820 | --- 821 | 822 | class: inverse 823 | name: debugstring 824 | 825 | .left-column-inverse[ 826 | ## Spark Glossary 827 | ### SparkContext 828 | ### Application 829 | ### Deployment 830 | ### Configuration 831 | ### Monitoring 832 | ] 833 | 834 | .right-column-inverse[ 835 | 836 | ### toDebugString 837 | 838 | ```scala 839 | scala> val wordCount = sc.textFile("README.md").flatMap(_.split("\\s+")).map((_, 1)).reduceByKey(_ + _) 840 | wordCount: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[21] at reduceByKey at :24 841 | 842 | scala> wordCount.toDebugString 843 | res13: String = 844 | (2) ShuffledRDD[21] at reduceByKey at :24 [] 845 | +-(2) MapPartitionsRDD[20] at map at :24 [] 846 | | MapPartitionsRDD[19] at flatMap at :24 [] 847 | | README.md MapPartitionsRDD[18] at textFile at :24 [] 848 | | README.md HadoopRDD[17] at textFile at :24 [] 849 | ``` 850 | ] 851 | 852 | ??? 853 | 854 | Spark的这种依赖关系设计,使其具有了天生的容错性,大大加快了Spark的执行速度。因为,RDD数据集通过“血缘关系”记住了它是如何从其它RDD中演变过来的,血缘关系记录的是粗颗粒度的转换操作行为,当这个RDD的部分分区数据丢失时,它可以通过血缘关系获取足够的信息来重新运算和恢复丢失的数据分区,由此带来了性能的提升。 855 | 856 | --- 857 | 858 | class: inverse 859 | name: monitoring 860 | 861 | .left-column-inverse[ 862 | ## Spark Glossary 863 | ### SparkContext 864 | ### Application 865 | ### Deployment 866 | ### Configuration 867 | ### Monitoring 868 | ### Tuning 869 | ] 870 | 871 | .right-column-inverse[ 872 | 873 | 如何从数据层面下手调优 874 | ``` 875 | 数据类型 (primitive) FileFormat(json / parquet / orc...) 876 | 文件分区、分桶 小文件问题 877 | 数据倾斜问题 单个 partition / task 的 workload 878 | ... 879 | ``` 880 | 如何从资源层面下手调优 881 | ``` 882 | Driver(内存的配置) 883 | Executor (内存、核数)大小的配置 884 | 所属 Yarn 队列资源的通盘考虑 885 | Hadoop集群“超售” - 网络、磁盘、NodeManager load... 886 | ... 887 | ``` 888 | 889 | 如何从代码层面下手调优 890 | 891 | ``` 892 | 避免 shuffle / 广播 893 | 预聚合 894 | kryo序列化 895 | RDD 复用 / 持久化 896 | 高性能算子 897 | RDD -> DataFrame/Dataset 898 | ``` 899 | ] 900 | 901 | --- 902 | class: inverse 903 | name: sub-agenda 904 | 905 | .left-column-inverse[ 906 | ## Spark之路 907 | ] 908 | 909 | .right-column-inverse[ 910 | 911 | #### 源码学习: 912 | - Spark源码 - https://github.com/apache/spark 913 | - 三方库源码 - https://spark-packages.org/ 914 | 915 | #### 文档学习: 916 | - 官方文档 - http://spark.apache.org/docs/latest/ 917 | - 官方博客 - https://databricks.com/blog 918 | 919 | #### 其他: 920 | - https://github.com/netease-bigdata/ne-spark-courseware 921 | - https://github.com/jaceklaskowski/mastering-apache-spark-book 922 | - 远离 王家林 923 | 924 | ] 925 | 926 | --- 927 | 928 | class: middle, center, inverse 929 | name: greetings 930 | # Q & A 931 | 932 | --- 933 | 934 | class: middle, center, inverse 935 | name: greetings 936 | # Thank You! 937 | ### [Kent Yao] 938 | 939 | 940 | 941 | This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. 942 | 943 | 944 | 945 | 947 | 962 | 963 | 964 | -------------------------------------------------------------------------------- /slides/spark_core/rdd_basics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RDD Basics 5 | 6 | 135 | 136 | 137 | 138 | 139 | class: center, middle, inverse 140 | 141 | ## [NetEase Spark Courses](https://netease-bigdata.github.io/ne-spark-courseware/) 142 | 143 | 144 | 145 | 146 | 147 | 148 | ## Spark RDD理解与性能调优 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | ??? 158 | 备注:标题 159 | 帮助信息:在网页端按H键可进入帮助页面 160 | 161 | --- 162 | 163 | class: center,inverse 164 | name: rdd 165 | 166 | # Agenda 167 | ## - 168 | ** About Me ** 169 | ## - 170 | ** RDD定义及特点 ** 171 | ** RDD Operations ** 172 | ** RDD 依赖 ** 173 | ** RDD Persist ** 174 | ** 相关链接 ** 175 | 176 | --- 177 | class: 178 | name: 179 | .left-column[ 180 | ## About Me 181 | ] 182 | .right-column[ 183 | ## 王斐 184 | 185 | 2018年校招加入网易,硕士期间研究Spark平台内存优化,目前在杭州研究院-数据科学中心负责Spark平台开发相关工作。 186 | ] 187 | --- 188 | 189 | 190 | class: 191 | name: 192 | 193 | .left-column[ 194 | ### What is Spark? 195 | 196 | ] 197 | .right-column[ 198 | Apache Spark™ is a unified analytics engine for large-scale data processing. 199 | 200 | 201 | 202 | ] 203 | --- 204 | 205 | 206 | class: 207 | name: 208 | 209 | .left-column[ 210 | ### RDD定义及特点 211 | #### RDD定义 212 | 213 | ] 214 | .right-column[ 215 | RDD(Resilient Distributed Datasets),弹性分布式数据集,是对分布式内存的抽象。 216 | 217 | 218 | 219 | 220 | 221 | ] 222 | 223 | ??? 224 | 首先讲一下rdd的定义,rdd的英文全称是resilient distributed dataset,弹性分布式数据集,是对分布式内存的抽象。可以看下左边这张图,可以看到这个虚线下面是hdfs,上面是rdd,看起来rdd和hdfs有点像,都是跨节点,然后hdfs是分块,rdd是分区。 225 | 我们看右边这张图,rdd的一个分区,拆开了里面是一个迭代器,我们知道迭代器并不是一个集合,而只是提供了一个访问集合的方法。它类似于Java里面的stream。是lazy的,只有在触发取数据才会返回数据。 226 | 所以rdd也是lazy的,并不是一声明就会进行计算,而是需要一个触发,才会进行计算。 227 | 迭代器里面是hasnext和next方法,当调用next,他返回被引用集合中的一条数据,称之为record。 228 | 229 | --- 230 | class: 231 | name: 232 | .left-column[ 233 | ### RDD定义及特点 234 | #### RDD定义 235 | #### RDD特点 236 | 237 | ] 238 | .right-column[ 239 | 240 | #### 弹性 241 | - 不可变 242 | 243 | - 血缘 244 | 245 | #### 分布式 246 | - 跨节点 247 | - 分区 248 | 249 | #### 延迟计算 250 | 251 | 252 | 253 | 254 | ] 255 | ??? 256 | 前面讲了rdd的定义和形态,这里讲下他的特点。 257 | rdd叫做弹性分布式数据集,首先讲一下弹性相关的特点。 258 | 不变性。不变性是函数式编程的基石。给人以安全感,就是说这个rdd不论在哪,只要输入一样,他的输出永远都一样,这就方便进行容错。也方便进行弹性调度,spark在观察到一个任务在某个节点运行特别缓慢,会为这个任务在其他资源丰富的节点创建备份任务,谁先运行完就使用谁的结果,因为他的结果是确定的。 259 | 然后是血缘,就是说对于一个rdd,我知道他的来龙去脉,就算中间过程执行出错,也能从源头重新来过,这就提供了很好的容错。 260 | 分布式的特点就比较明朗,就是跨节点和分区,跨节点提供了多个节点一起执行的能力,而分区,将要处理的数据的粒度进行划分的更细,方便进行并行操作。 261 | 延迟计算前面已经提到过,分区里面是迭代器,这是一个lazy访问,比如下面这一大串的rdd,组成了一个链,在触发图上这个runjob之前,前面的abcdef都不会发生计算。 262 | 这样做有优点也有缺点,优点就是说,这样可以更明确的进行计算,比如我一个应用我要做五个job,这五件事情是相互独立的。如果没有延迟计算,那五件job,第一件开始了,我开始算,第二件开始了,我也算,同时五个job一起算,可能内存中就存储了大量的中间数据,结果这些资源可能死锁,五个job一件都完成不了。 263 | 而延迟计算就比较明确,一件job触发runjob我才计算,完成一个job再去完成另外的job,这样效率更高。 264 | 然后就是延迟计算,可以得到整个job的拓扑图,可以对这个拓扑图进行优化。 265 | 延迟计算的缺点,就是响应会慢点,这就导致spark在流处理这块,响应速度比Flink要慢,不适合实时流计算场景。但是目前spark也在流计算方面追赶,相信未来spark在流处理方面会有更好地表现。 266 | 267 | 268 | 269 | 270 | --- 271 | class: 272 | name: 273 | .left-column[ 274 | ## RDD Operations 275 | 277 | ### transformation 278 | ] 279 | .right-column[ 280 | transformation:从数据源生成RDD或者对已存在的RDD进行转换生成新RDD。 281 | - transforamtion算子提供了一个并行的语义。 282 | 283 | - API隐藏了数据划分、并行、通信、容错等复杂的框架代码。 284 | 285 | - 算子里面的函数为用户自定义的函数(UDF),该函数为一个串行函数,根据算子的语义对RDD中的数据按照语义的**操作粒度**进行操作。 286 | 1. rdd1.mapValues(s=> s*2) 287 | 2. def func1( i:Int):Int={ i*2} rdd1.mapValues (func1) 288 | 289 | |transformation | Meaning | 290 | | ----- | :-----: | 291 | | textFile/objectFile| 从数据源生成RDD| 292 | | map(func) | 对每条record进行函数计算 | 293 | | mapValues(func)| 对(key,value)类型的record的value计算 | 294 | | filter(func) | 只保留符合条件的records | 295 | | flatMap(func) | 将records的按照规则进行展开 | 296 | | mapPartitions(func) | 以RDD的每个分区进行函数计算 | 297 | | mapPartitionsWithIndex(func) | 对分区操作,提供partitionId参数 | 298 | | groupByKey([numPartitions]) | 对(k,v)类型records按照key进行聚合 | 299 | | reduceByKey(func, [numPartitions]) | 对(k,v)类型records按照key进行合并 | 300 | | aggregateByKey(zeroValue)(seqOp, combOp,[numPartitions]) | 先按照分区聚合,然后按照key值合并 | 301 | | coalesce(numPartitions) | 重新分区 | 302 | | repartition(numPartitions) | 重新分区 | 303 | 304 | 306 | 307 | 315 | 316 | ] 317 | ??? 318 | spark的提供了丰富的算子,相对于MapReduce编程模型,需要自己指定mapper和reducer方便了很多。 319 | 算子分为两种,trans和action。首先讲一下trans,字面意思是转换。是生成新的RDD,要么是从数据中生成,或者是对现有RDD进行操作生成新的。 320 | 下面列的这些都是常见的trans算子,比如map,mapvalue,mappartitions这些。trans算子提供了并行的语义,把底层的并行进行了封装,什么数据划分都不用我们管,往往需要用户提供一个函数参数,这个函数是一个串行函数,然后加上算子的并行的语义,就可以并行的对数据进行计算。至于对哪些数据进行计算,是对一条数据,还是一个分区的数据,是需要看算子的操作粒度的。 321 | 322 | 342 | --- 343 | class: 344 | name: 345 | .left-column[ 346 | ## RDD Operations 347 | 349 | ### transformation 350 | ] 351 | .right-column[ 352 | 353 | #### 操作粒度 354 | 355 | - 对每条record操作 356 | 357 | map, flatMap, filter 358 | 359 | - 对(key, value)类型record中的value操作 360 | 361 | mapValues 362 | 363 | - 对整个分区数据操作 364 | 365 | mapPartitions,mapPartitionsWithIndex 366 | 367 | - 对分区进行混洗(shuffle) 368 | 369 | reduceBykey,aggregataByKey,gropuByKey,repartition 370 | 371 | repartition,repartitionAndSortWithinPartitions 372 | 373 | 380 | 381 | 382 | 383 | ] 384 | 385 | --- 386 | 387 | class: 388 | name: 389 | .left-column[ 390 | ## RDD Operations 391 | ### transformation 392 | ### action 393 | 394 | ] 395 | .right-column[ 396 | 397 | Action:得到一个结果,或者将RDD存入磁盘。 398 | 399 | | Action | Meaning | 400 | | :----------: | :--------------------------------------: | 401 | | countByKey() | 返回Map(k,count(k)),即每个key的个数 | 402 | | reduce(func) | 将所有数据按照func进行聚合,返回一个值 | 403 | | take(n) | 返回数据集的前N个数据的数组 | 404 | | collect() | 将所有数据提取到driver上,转换成一个数组 | 405 | | count() | 获得数据集的数据条数,一个值 | 406 | | first() | 返回数据集的第一个数据,一个值| 407 | |foreach(func) | 对每个数据进行操作 | 408 | | takeOrdered(n, [ordering]) | 返回排序后数据集的前n个数据,一个数组 | 409 | | takeSample(withReplacement, num, [seed]) | 根据seed进行抽样,获得num个数据,一个数组 | 410 | | saveAsTextFile(path) | 保存为text文件 | 411 | | saveAsObjectFile(path) | 保存为object文件 | 412 | ] 413 | 414 | --- 415 | 416 | class: 417 | name: 418 | .left-column[ 419 | ## RDD Operations 420 | ### transformation 421 | ### action 422 | ### 理解算子 423 | ] 424 | .right-column[ 425 | 426 | - map, mapPartitions 427 | 428 | 例子: 一个RDD有10个分区,每个分区有1000条数据。对每条数据进行function操作。 429 | 430 | map算子,调用function 10000次 431 | 432 | mapPartitions算子,调用function10次,但是每次处理一个分区的数据,分区较大可能发生OOM 433 | 434 | ``` 435 | // rdd:RDD[(Int,Int)] 436 | def function1(tuple:(Int,Int)):(Int,Int)={ 437 | (tuple._1+1,tuple._2+1) 438 | } 439 | def function2(iterator: Iterator[(Int,Int)]): Iterator[(Int,Int)] ={ 440 | var list=List[(Int,Int)]() 441 | for(i<-iterator){ 442 | list.::(i._1+1,i._2+1) 443 | } 444 | list.iterator 445 | } 446 | rdd.map(function1) 447 | rdd.mapPartitions(function2) 448 | ``` 449 | 450 | - collect 451 | 452 | collect算子会拉取rdd中所有数据到driver节点,转换成一个数组,如果数据量过大,会造成driver的OOM 453 | 454 | 458 | 459 | 460 | 466 | 467 | ] 468 | 469 | 470 | 471 | 472 | --- 473 | class: 474 | name: 475 | .left-column[ 476 | ## RDD Operations 477 | ### transformation 478 | ### action 479 | ### 理解算子 480 | ### wordCount Demo 481 | ] 482 | .right-column[ 483 | 484 | ``` 485 | package com.netease.bigdata.spark 486 | 487 | import org.apache.spark.{SparkConf, SparkContext} 488 | 489 | object WordCount { 490 | 491 | def main(args: Array[String]): Unit = { 492 | require(args.length == 1, "Usage: WordCount ") 493 | val conf = new SparkConf().setAppName("Word Count").setMaster("local[*]") 494 | val sc = new SparkContext(conf) 495 | val textFile = sc.textFile(args(0), 2) 496 | val words = textFile.flatMap(_.split("\\s+")) 497 | val ones = words.map((_, 1)) 498 | val counts = ones.reduceByKey(_ + _) 499 | val res = counts.collect() 500 | for ((word, count) <- res) { 501 | println(word + ": " + count) 502 | } 503 | 504 | sc.stop() 505 | } 506 | 507 | } 508 | ``` 509 | 510 | 511 | 512 | 513 | 514 | 515 | ] 516 | --- 517 | class: 518 | name: 519 | .left-column[ 520 | ## RDD 依赖 521 | ### 宽依赖 & 窄依赖 522 | 523 | ] 524 | .right-column[ 525 | 526 | 527 | 528 | 529 | 530 | ] 531 | --- 532 | class: 533 | name: 534 | .left-column[ 535 | ## RDD 依赖 536 | ### 宽依赖 & 窄依赖 537 | ### Shuffle 538 | 539 | ] 540 | .right-column[ 541 | 542 | 543 | #### Shuffle是一个怎样的过程: SortShuffle 544 | 545 | - shuffle write端每个task计算结果按照key进行hash得到partitionID,然后把((pID,key),value)插入到一个数组(内存)中,如果内存不足则spill到磁盘,对数组使用TimSort排序(内存不足则spill到磁盘) 546 | 547 | - 将排序结果存为partitiondFile到磁盘(序列化,磁盘I/O). 548 | 549 | - shuffle read端从所有partitionFile中拉取对应分区数据,进行网络传输,反序列化为对象,进行合并排序,内存不足则溢出磁盘. 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | ] 562 | 563 | --- 564 | class: 565 | name: 566 | .left-column[ 567 | ## RDD 依赖 568 | ### 宽依赖 & 窄依赖 569 | ### Shuffle 570 | ### Shuffle & Stage & Task 571 | 572 | ] 573 | .right-column[ 574 | #### WordCount Lineage 575 | 576 | 577 | 578 | 579 | 580 | - Action触发Job的提交 581 | - 按照Shuffle划分stages 582 | - 每个stage中是独立的n个task,n等于当前stage中rdd分区的数目,每个task分别处理一个分区的数据 583 | - 在一个executor中,并行的task数目和executor的核数有关。 584 | 585 | 586 | 587 | ] 588 | --- 589 | class: 590 | name: 591 | .left-column[ 592 | ## RDD 依赖 593 | ### 宽依赖 & 窄依赖 594 | ### Shuffle 595 | ### Shuffle & Stage & Task 596 | ### Task & Loop 597 | 598 | ] 599 | .right-column[ 600 | 601 | ##### Loop 602 | - 从数据源中读取数据对象 603 | - 对每个数据对象运用一系列计算函数 604 | - 将计算结果写入一个新的数据集合中 605 | 606 | 607 | 608 | 609 | ] 610 | --- 611 | class: 612 | name: 613 | .left-column[ 614 | ## RDD 依赖 615 | ### 宽依赖 & 窄依赖 616 | ### Shuffle 617 | ### Shuffle & Stage & Task 618 | ### Task & Loop 619 | ### Object Lifetime 620 | 621 | ] 622 | .right-column[ 623 | 三种数据容器:存放数据对象的引用,数据对象的生命周期依赖于其容器的生命周期。 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | ] 635 | 636 | --- 637 | class: 638 | name: 639 | .left-column[ 640 | ## RDD 依赖 641 | ### 宽依赖 & 窄依赖 642 | ### Shuffle 643 | ### Shuffle & Stage & Task 644 | ### Task & Loop 645 | ### Object Lifetime 646 | ### Shuffle Tuning 647 | 648 | ] 649 | .right-column[ 650 | #### 避免不必要shuffle 651 | 652 | - repartition, coalesce, repartitionandsortwithinpartitions 对rdd重新分区 653 | 654 | repartition(numPartitions: Int)=coalesce(numPartitions, shuffle = true) 655 | 656 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | ] 667 | --- 668 | class: 669 | name: 670 | .left-column[ 671 | ## RDD 依赖 672 | ### 宽依赖 & 窄依赖 673 | ### Shuffle 674 | ### Shuffle & Stage & Task 675 | ### Task & Loop 676 | ### Object Lifetime 677 | ### Shuffle Tuning 678 | 679 | ] 680 | .right-column[ 681 | - broadcast实现map join代替 reduce join,**避免shuffle** 682 | 683 | 在对RDD使用join类操作(with inputs not co-partitioned),而且join操作中的一个RDD或表的数据量比较小 684 | 685 | 使用broadcast广播小表,然后通过对小表进行遍历完成map join,避免了shuffle的发生 686 | 687 | ``` 688 | val rdd2Data = rdd2.collect() 689 | val rdd2Bc = sc.broadcast(rdd2Data) 690 | def function(tuple: (String,Int)): (String,(Int,String)) ={ 691 | for(value <- rdd2Bc.value){ 692 | if(value._1.equals(tuple._1)) 693 | return (tuple._1,(tuple._2,value._2.toString)) 694 | } 695 | (tuple._1,(tuple._2,null)) 696 | } 697 | val rdd3 = rdd1.map(function(_)) 698 | 699 | ``` 700 | 701 | 702 | 703 | ] 704 | 705 | --- 706 | class: 707 | name: 708 | .left-column[ 709 | ## RDD 依赖 710 | ### 宽依赖 & 窄依赖 711 | ### Shuffle 712 | ### Shuffle & Stage & Task 713 | ### Task & Loop 714 | ### Object Lifetime 715 | ### Shuffle Tuning 716 | 717 | ] 718 | .right-column[ 719 | 720 | - Why broadcast? 721 | 722 | Spark存在作用域,变量声明在driver上,当task需要操作这些driver上声明的变量时会从driver拷贝副本传输到task。 723 | 724 | broadcast是保证这个由driver声明的变量值只会发送到每个worker上面一份。 725 | 726 | 如果不使用broadcast,driver需要给每个task都发送一份副本,如果广播变量较大,会造成大量网络传输。 727 | 728 | ``` 729 | val rdd2Data = rdd2.collect() 730 | //val rdd2Bc = sc.broadcast(rdd2Data) 731 | def function(tuple: (String,Int)): (String,(Int,String)) ={ 732 | for(value <- rdd2Data){ 733 | if(value._1.equals(tuple._1)) 734 | return (tuple._1,(tuple._2,value._2.toString)) 735 | } 736 | (tuple._1,(tuple._2,null)) 737 | } 738 | val rdd3 = rdd1.map(function(_)) 739 | 740 | ``` 741 | 742 | ] 743 | 744 | --- 745 | class: 746 | name: 747 | .left-column[ 748 | ## RDD 依赖 749 | ### 宽依赖 & 窄依赖 750 | ### Shuffle 751 | ### Shuffle & Stage & Task 752 | ### Task & Loop 753 | ### Object Lifetime 754 | ### Shuffle Tuning 755 | 756 | ] 757 | .right-column[ 758 | #### map-side Combine 759 | 760 | 在shuffle write端进行合并数据,可以减少shuffle阶段序列化反序列化开销以及网络传输开销,也会减小在shuffle read端的压力,提升程序性能。 761 | 762 | - 尽量使用aggregateByKey和reduceByKey代替groupByKey 763 | 764 | | | | 765 | | :---:| :---: | 766 | | groupByKey([numPartitions]) | 没有map-side combine,对(k,v)类型records按照key进行聚合 | 767 | | reduceByKey(func, [numPartitions]) | map-side combine, 对(k,v)类型records按照key进行合并 | 768 | | aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions]) | 先对数据按照分区使用seqOp聚合,然后再按照key值使用combOq合并 | 769 | 770 | 一个RDD,变量名为rdd:RDD[(Int,Int)],对其进行按key,求value之和操作。 771 | 772 | ``` 773 | //reduceByKey with map-side combine 774 | val red=rdd.reduceByKey(_+_) 775 | //aggregateByKey with map-side combine 776 | val agg=rdd.aggregateByKey(0)(((i1,i2)=>i1+i2),((i1,i2)=>(i1+i2))) 777 | //groupByKey without map-side combine 778 | val gbk=rdd.groupByKey().mapValues(iter=> iter.sum) 779 | ``` 780 | 781 | ] 782 | 783 | --- 784 | class: 785 | name: 786 | .left-column[ 787 | ## RDD 依赖 788 | ### 宽依赖 & 窄依赖 789 | ### Shuffle 790 | ### Shuffle & Stage & Task 791 | ### Task & Loop 792 | ### Object Lifetime 793 | ### Shuffle Tuning 794 | 795 | ] 796 | .right-column[ 797 | #### 数据倾斜 798 | 数据倾斜是由于存在一些**热点数据**,比如某个key存在大量对应的value,或者某个分区存在大量数据(即存在大量hash之后得到同样hash值的key)。 799 | 800 | ##### 数据倾斜现象 801 | - 绝大多数task很快结束,存在几个straggler. 802 | - 原本能够正常执行的Spark作业,执行某个数据集突然报出OOM(内存溢出)异常。 803 | 804 | ##### 数据倾斜原理及影响 805 | - shuffle Read需要将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或join等操作。此时如果某个key对应的数据量特别大的话,就会发生数据倾斜。 806 | 807 | - 某个task对应数据量大,可能会导致OOM,以及多次重试。 808 | 809 | - 每个stage的运行时间由最后一个完成的task决定。 810 | ] 811 | --- 812 | class: 813 | name: 814 | .left-column[ 815 | ## RDD 依赖 816 | ### 宽依赖 & 窄依赖 817 | ### Shuffle 818 | ### Shuffle & Stage & Task 819 | ### Task & Loop 820 | ### Object Lifetime 821 | ### Shuffle Tuning 822 | 823 | ] 824 | .right-column[ 825 | #### 解决数据倾斜 826 | 827 | - 提高shuffle操作的并行度-加大shuffle操作时partition数量 828 | 829 | reduceByKey(func, [numPartitions]) 830 | 831 | aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions]) 832 | 833 | 配置spark.default.parallelism 834 | 835 | 优点: 实现简单,可以有效缓解数据倾斜。 836 | 837 | 缺点: 针对热点数据比如一个key对应巨量数据的情况无法解决。 838 | 839 | - 分阶段聚合 840 | 841 | 前面提到的map-side combine 842 | 843 | 优点: 对于聚合类的shuffle操作导致的数据倾斜,非常有效。 844 | 845 | 缺点: 仅仅适用于聚合类的shuffle操作,适用范围相对较窄。无法处理join类shuffle数据倾斜。 846 | 847 | ] 848 | 849 | 850 | --- 851 | class: 852 | name: 853 | .left-column[ 854 | ## RDD 依赖 855 | ### 宽依赖 & 窄依赖 856 | ### Shuffle 857 | ### Shuffle & Stage & Task 858 | ### Task & Loop 859 | ### Object Lifetime 860 | ### Shuffle Tuning 861 | 862 | ] 863 | .right-column[ 864 | 865 | - 给key加随机前缀,缓解热点数据 866 | 867 | 通过spark提供的takeSample算子可以对RDD进行采样。通过观察看是否存在数据倾斜/热点数据. 868 | 869 | ``` 870 | takeSample(withReplacement, num, [seed]) 871 | ``` 872 | 873 | 进行join操作,比如存在热点key “hello",对应大量的value,此时我们给每个key加上(0-10)之间的随机前缀,这些数据就会随机变成(1_hello,v1),(2_hello,v2) ... (10_hello,v10),这样就缓解了热点的key。 874 | 875 | 876 | 877 | 878 | - 混合使用多种调优策略 879 | 880 | 881 | ] 882 | 883 | 905 | 906 | 907 | 928 | 929 | 930 | 931 | 952 | 953 | 954 | 976 | 977 | --- 978 | class: 979 | name: 980 | .left-column[ 981 | ## RDD 依赖 982 | ### 宽依赖 & 窄依赖 983 | ### Shuffle 984 | ### Shuffle & Stage & Task 985 | ### Task & Loop 986 | ### Object Lifetime 987 | ### Shuffle Tuning 988 | 989 | ] 990 | .right-column[ 991 | 992 | #### 参数调优 993 | 994 | - spark.shuffle.file.buffer 995 | 996 | default: 32k 997 | 998 | shuffle write端写磁盘文件时缓冲区大小,适量增大可以减少磁盘I/O次数,进而提升性能。 999 | 1000 | - spark.reducer.maxSizeInFlight 1001 | 1002 | default: 48M 1003 | 1004 | shuffle read端拉取对应分区数据缓冲区大小,适量增大可以减少网络传输次数,进而提升性能。 1005 | 1006 | - spark.shuffle.io.maxRetries 1007 | 1008 | default: 3 1009 | 1010 | shuffle read端拉取对应数据时,因为网络异常拉取失败重新尝试的最大次数。针对超大数据量的应用,可以增大重试次数,大幅度提升稳定性。 1011 | 1012 | 1013 | 1014 | ] 1015 | 1016 | 1017 | --- 1018 | class: 1019 | name: 1020 | 1021 | 1022 | .left-column[ 1023 | ## RDD Persist 1024 | 1025 | 1026 | ] 1027 | .right-column[ 1028 | - 机器学习,图计算等应用存在大量迭代计算。 1029 | 1030 | - 适当的缓存中间数据可以避免重复计算。 1031 | 1032 | persist(storageLevel) 1033 | 1034 | cache()=persist(StorageLevel.MEMORY_ONLY) 1035 | 1036 | - 缓存级别 1037 | 1038 | | | | 1039 | | :-----------------: | :----:| 1040 | | MEMORY_ONLY | 只缓存在内存中| 1041 | | MEMORY_AND_DISK | 缓存在内存和磁盘| 1042 | | MEMORY_ONLY_SER | 序列化缓存在内存中| 1043 | | MEMORY_AND_DISK_SER | 序列化缓存在内存和磁盘| 1044 | | DISK_ONLY | 只缓存在磁盘| 1045 | | MEMORY_ONLY_2 | 缓存在内存中两份(一份副本)| 1046 | | MEMORY_AND_DISK_2 | 缓存在内核和磁盘两份(一份副本)| 1047 | | OFF_HEAP | 缓存在堆外 | 1048 | 1049 | 1050 | 1051 | ] 1052 | --- 1053 | class: 1054 | name: 1055 | 1056 | 1057 | .left-column[ 1058 | ## 相关链接 1059 | 1060 | 1061 | ] 1062 | .right-column[ 1063 | 1064 | - RDD论文 1065 | 1066 | https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final138.pdf 1067 | 1068 | - RDD算子介绍 1069 | 1070 | http://spark.apache.org/docs/latest/rdd-programming-guide.html 1071 | 1072 | - spark 配置 1073 | 1074 | http://spark.apache.org/docs/latest/configuration.html 1075 | 1076 | - 性能调优 1077 | 1078 | http://spark.apache.org/docs/latest/tuning.html 1079 | 1080 | 1081 | 1082 | ] 1083 | --- 1084 | 1085 | class: middle, center, inverse 1086 | name: greetings 1087 | # Q & A 1088 | --- 1089 | class: center, middle, inverse 1090 | 1091 | # Thanks! 1092 | 1093 | 1094 | 1095 | This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. 1096 | 1097 | 1098 | 1100 | 1115 | 1116 | 1117 | --------------------------------------------------------------------------------