未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合已有相应语言基础、能自行准备Spark环境的读者,用内置小样本对照学习WordCount的RDD、DataFrame和SQL实现;本次仅作全文结构审阅,未进行编译、运行或外部事实核验。正文尚未形成完整的构建交付流程:Java仅给出Maven依赖片段,缺少项目坐标、编译配置和打包步骤,不能据此直接生成提交命令中的JAR;其SQL文本块需要Java 15及以上语法支持,读者还须确认JDK与Spark 3.4.0的兼容性。Scala也需自行完成源码组织和打包。标为Scala、Python基础语法的链接实际指向R文章,不能作为对应语言前置。集群提交需另备集群环境,文件与socket扩展练习需自行提供可访问的文件和端口数据源;调试、优化片段依赖已有上下文,不是独立应用。
第一个Spark程序开发实战
概述
本指南将通过完整的示例代码,带你开发第一个Spark应用程序。我们将使用三种主要编程语言(Scala、Python、Java)来实现相同的功能,让你理解Spark程序的基本结构和开发模式。
Spark程序基本结构
程序架构图
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph TD
classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
subgraph "Spark应用程序结构"
Driver[Driver Program<br/>驱动程序]
subgraph "SparkContext/SparkSession"
SC[SparkContext]
SS[SparkSession]
end
subgraph "数据抽象"
RDD[RDD<br/>弹性分布式数据集]
DF[DataFrame<br/>结构化数据]
DS[Dataset<br/>类型安全数据]
end
subgraph "执行引擎"
Executor1[Executor 1]
Executor2[Executor 2]
ExecutorN[Executor N]
end
Driver --> SC
Driver --> SS
SC --> RDD
SS --> DF
SS --> DS
Driver --> Executor1
Driver --> Executor2
Driver --> ExecutorN
end
class Driver,SC,SS,RDD,DF,DS,Executor1,Executor2,ExecutorN default
核心概念
| 概念 | 说明 | 作用 |
|---|---|---|
| Driver Program | 驱动程序 | 包含main函数,创建SparkContext |
| SparkContext | Spark上下文 | 连接集群的入口点 |
| SparkSession | Spark会话 | 统一的编程接口(Spark 2.0+) |
| RDD | 弹性分布式数据集 | Spark的基础数据抽象 |
| DataFrame | 结构化数据框 | 带Schema的分布式数据集 |
开发环境准备
IDE配置
IntelliJ IDEA + Scala
// build.sbt
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.15"
lazy val root = (project in file("."))
.settings(
name := "spark-first-app",
libraryDependencies ++= Seq(
"org.apache.spark" %% "spark-core" % "3.4.0",
"org.apache.spark" %% "spark-sql" % "3.4.0",
"org.apache.spark" %% "spark-mllib" % "3.4.0" % "provided"
)
)
VS Code + Python
# requirements.txt
pyspark==3.4.0
findspark==2.0.1
jupyter==1.0.0
pandas==1.5.3
matplotlib==3.6.3
Eclipse + Java
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
第一个程序:WordCount
Scala版本
// WordCountApp.scala
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
object WordCountApp {
def main(args: Array[String]): Unit = {
// 创建SparkSession
val spark = SparkSession.builder()
.appName("WordCount Application")
// 执行环境由spark-submit的--master指定;本地练习可使用--master 'local[*]'
.config("spark.sql.adaptive.enabled", "true")
.getOrCreate()
import spark.implicits._
try {
// 设置日志级别
spark.sparkContext.setLogLevel("WARN")
println("=== Spark WordCount Application ===")
// 方法1:使用RDD API
wordCountWithRDD(spark)
// 方法2:使用DataFrame API
wordCountWithDataFrame(spark)
// 方法3:使用SQL API
wordCountWithSQL(spark)
} finally {
// 关闭SparkSession
spark.stop()
}
}
// 使用RDD实现WordCount
def wordCountWithRDD(spark: SparkSession): Unit = {
println("\n--- 使用RDD API ---")
val sc = spark.sparkContext
// 创建测试数据
val textData = sc.parallelize(Seq(
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
))
// WordCount核心逻辑
val wordCounts = textData
.flatMap(_.split(" ")) // 分割单词
.map(word => word.toLowerCase) // 转小写
.filter(_.nonEmpty) // 过滤空字符串
.map((_, 1)) // 转换为(word, 1)
.reduceByKey(_ + _) // 按key聚合
.sortBy(-_._2) // 按计数降序排列
// 收集并打印结果
val results = wordCounts.collect()
results.foreach { case (word, count) =>
println(f"$word%15s: $count%3d")
}
println(s"总词汇数: ${results.length}")
}
// 使用DataFrame实现WordCount
def wordCountWithDataFrame(spark: SparkSession): Unit = {
println("\n--- 使用DataFrame API ---")
import spark.implicits._
// 创建DataFrame
val textDF = Seq(
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
).toDF("text")
// 使用DataFrame操作
val wordCountsDF = textDF
.select(explode(split(lower(col("text")), " ")).alias("word"))
.filter(col("word") =!= "")
.groupBy("word")
.count()
.orderBy(desc("count"))
wordCountsDF.show()
println(s"使用DataFrame统计的总词汇数: ${wordCountsDF.count()}")
}
// 使用SQL实现WordCount
def wordCountWithSQL(spark: SparkSession): Unit = {
println("\n--- 使用SQL API ---")
import spark.implicits._
// 创建临时视图
val textDF = Seq(
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
).toDF("text")
textDF.createOrReplaceTempView("text_data")
// 使用SQL查询
val sqlQuery = """
SELECT word, COUNT(*) as count
FROM (
SELECT LOWER(word) as word
FROM (
SELECT EXPLODE(SPLIT(text, ' ')) as word
FROM text_data
) t
WHERE word != ''
) word_table
GROUP BY word
ORDER BY count DESC
"""
val resultDF = spark.sql(sqlQuery)
resultDF.show()
// 获取最常见的单词
val mostCommon = resultDF.first()
println(s"最常见的单词: '${mostCommon.getString(0)}' 出现 ${mostCommon.getLong(1)} 次")
}
}
Python版本
# wordcount_app.py
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import *
import os
def create_spark_session():
"""创建SparkSession"""
return SparkSession.builder \
.appName("WordCount Application - Python") \
.config("spark.sql.adaptive.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
.getOrCreate()
def word_count_with_rdd(spark):
"""使用RDD API实现WordCount"""
print("\n--- 使用RDD API ---")
sc = spark.sparkContext
# 创建测试数据
text_data = sc.parallelize([
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
])
# WordCount核心逻辑
word_counts = text_data \
.flatMap(lambda line: line.split(" ")) \
.map(lambda word: word.lower()) \
.filter(lambda word: word != "") \
.map(lambda word: (word, 1)) \
.reduceByKey(lambda a, b: a + b) \
.sortBy(lambda x: -x[1])
# 收集并打印结果
results = word_counts.collect()
for word, count in results:
print(f"{word:>15}: {count:>3}")
print(f"总词汇数: {len(results)}")
def word_count_with_dataframe(spark):
"""使用DataFrame API实现WordCount"""
print("\n--- 使用DataFrame API ---")
# 创建DataFrame
text_data = [
("Hello Spark World",),
("Spark is fast",),
("Spark is easy to use",),
("Big Data processing with Spark",)
]
schema = StructType([StructField("text", StringType(), True)])
text_df = spark.createDataFrame(text_data, schema)
# 使用DataFrame操作
word_counts_df = text_df \
.select(explode(split(lower(col("text")), " ")).alias("word")) \
.filter(col("word") != "") \
.groupBy("word") \
.count() \
.orderBy(desc("count"))
word_counts_df.show()
print(f"使用DataFrame统计的总词汇数: {word_counts_df.count()}")
def word_count_with_sql(spark):
"""使用SQL API实现WordCount"""
print("\n--- 使用SQL API ---")
# 创建DataFrame并注册为临时视图
text_data = [
("Hello Spark World",),
("Spark is fast",),
("Spark is easy to use",),
("Big Data processing with Spark",)
]
schema = StructType([StructField("text", StringType(), True)])
text_df = spark.createDataFrame(text_data, schema)
text_df.createOrReplaceTempView("text_data")
# 使用SQL查询
sql_query = """
SELECT word, COUNT(*) as count
FROM (
SELECT LOWER(word) as word
FROM (
SELECT EXPLODE(SPLIT(text, ' ')) as word
FROM text_data
) t
WHERE word != ''
) word_table
GROUP BY word
ORDER BY count DESC
"""
result_df = spark.sql(sql_query)
result_df.show()
# 获取最常见的单词
most_common = result_df.first()
print(f"最常见的单词: '{most_common['word']}' 出现 {most_common['count']} 次")
def analyze_performance(spark):
"""性能分析"""
print("\n--- 性能分析 ---")
# 生成大量测试数据
import time
import random
words = ["spark", "hadoop", "python", "scala", "java", "data", "big", "analytics"]
large_data = [" ".join(random.choices(words, k=10)) for _ in range(10000)]
start_time = time.time()
rdd = spark.sparkContext.parallelize(large_data)
word_count = rdd \
.flatMap(lambda line: line.split(" ")) \
.map(lambda word: (word, 1)) \
.reduceByKey(lambda a, b: a + b) \
.count()
end_time = time.time()
print(f"处理10000行数据,耗时: {end_time - start_time:.2f} 秒")
print(f"唯一词汇数: {word_count}")
def main():
"""主函数"""
print("=== Spark WordCount Application - Python ===")
# 创建SparkSession
spark = create_spark_session()
# 设置日志级别
spark.sparkContext.setLogLevel("WARN")
try:
# 执行不同版本的WordCount
word_count_with_rdd(spark)
word_count_with_dataframe(spark)
word_count_with_sql(spark)
analyze_performance(spark)
# 显示Spark配置信息
print("\n--- Spark配置信息 ---")
print(f"Spark版本: {spark.version}")
print(f"应用名称: {spark.sparkContext.appName}")
print(f"Master URL: {spark.sparkContext.master}")
print(f"默认并行度: {spark.sparkContext.defaultParallelism}")
finally:
# 关闭SparkSession
spark.stop()
print("\nSpark应用程序执行完成!")
if __name__ == "__main__":
main()
Java版本
// WordCountApp.java
package com.example.spark;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import scala.Tuple2;
import java.util.Arrays;
import java.util.List;
import static org.apache.spark.sql.functions.*;
public class WordCountApp {
public static void main(String[] args) {
System.out.println("=== Spark WordCount Application - Java ===");
// 创建SparkSession
SparkSession spark = SparkSession.builder()
.appName("WordCount Application - Java")
// 执行环境由spark-submit的--master指定
.config("spark.sql.adaptive.enabled", "true")
.getOrCreate();
// 设置日志级别
spark.sparkContext().setLogLevel("WARN");
try {
// 执行不同版本的WordCount
wordCountWithRDD(spark);
wordCountWithDataFrame(spark);
wordCountWithSQL(spark);
} finally {
// 关闭SparkSession
spark.stop();
System.out.println("\nSpark应用程序执行完成!");
}
}
/**
* 使用RDD API实现WordCount
*/
public static void wordCountWithRDD(SparkSession spark) {
System.out.println("\n--- 使用RDD API ---");
JavaSparkContext jsc = JavaSparkContext.fromSparkContext(spark.sparkContext());
// 创建测试数据
List<String> textData = Arrays.asList(
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
);
JavaRDD<String> textRDD = jsc.parallelize(textData);
// WordCount核心逻辑
JavaPairRDD<String, Integer> wordCounts = textRDD
.flatMap(line -> Arrays.asList(line.split(" ")).iterator())
.map(String::toLowerCase)
.filter(word -> !word.isEmpty())
.mapToPair(word -> new Tuple2<>(word, 1))
.reduceByKey(Integer::sum)
.sortByKey();
// 收集并打印结果
List<Tuple2<String, Integer>> results = wordCounts.collect();
results.forEach(tuple ->
System.out.printf("%15s: %3d%n", tuple._1(), tuple._2()));
System.out.println("总词汇数: " + results.size());
}
/**
* 使用DataFrame API实现WordCount
*/
public static void wordCountWithDataFrame(SparkSession spark) {
System.out.println("\n--- 使用DataFrame API ---");
// 创建DataFrame
List<String> textData = Arrays.asList(
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
);
Dataset<Row> textDF = spark.createDataset(textData,
org.apache.spark.sql.Encoders.STRING()).toDF("text");
// 使用DataFrame操作
Dataset<Row> wordCountsDF = textDF
.select(explode(split(lower(col("text")), " ")).alias("word"))
.filter(col("word").notEqual(""))
.groupBy("word")
.count()
.orderBy(desc("count"));
wordCountsDF.show();
System.out.println("使用DataFrame统计的总词汇数: " + wordCountsDF.count());
}
/**
* 使用SQL API实现WordCount
*/
public static void wordCountWithSQL(SparkSession spark) {
System.out.println("\n--- 使用SQL API ---");
// 创建DataFrame并注册为临时视图
List<String> textData = Arrays.asList(
"Hello Spark World",
"Spark is fast",
"Spark is easy to use",
"Big Data processing with Spark"
);
Dataset<Row> textDF = spark.createDataset(textData,
org.apache.spark.sql.Encoders.STRING()).toDF("text");
textDF.createOrReplaceTempView("text_data");
// 使用SQL查询
String sqlQuery = """
SELECT word, COUNT(*) as count
FROM (
SELECT LOWER(word) as word
FROM (
SELECT EXPLODE(SPLIT(text, ' ')) as word
FROM text_data
) t
WHERE word != ''
) word_table
GROUP BY word
ORDER BY count DESC
""";
Dataset<Row> resultDF = spark.sql(sqlQuery);
resultDF.show();
// 获取最常见的单词
Row mostCommon = resultDF.first();
System.out.println("最常见的单词: '" + mostCommon.getString(0) +
"' 出现 " + mostCommon.getLong(1) + " 次");
}
}
程序运行方式
1. 本地开发模式
# Scala版本
spark-submit \
--class WordCountApp \
--master local[4] \
target/scala-2.12/spark-first-app_2.12-0.1.0-SNAPSHOT.jar
# Python版本
spark-submit \
--master local[4] \
wordcount_app.py
# Java版本
spark-submit \
--class com.example.spark.WordCountApp \
--master local[4] \
target/spark-first-app-1.0-SNAPSHOT.jar
2. 集群提交模式
# 提交到YARN集群
spark-submit \
--class WordCountApp \
--master yarn \
--deploy-mode cluster \
--executor-memory 2g \
--executor-cores 2 \
--num-executors 4 \
spark-first-app.jar
# 提交到Standalone集群
spark-submit \
--class WordCountApp \
--master spark://master:7077 \
--deploy-mode cluster \
--total-executor-cores 8 \
--executor-memory 2g \
spark-first-app.jar
3. 交互式开发
# Scala Shell
spark-shell --master local[4]
scala> :paste
// 粘贴代码片段
val textRDD = sc.parallelize(Array("Hello Spark", "Spark is great"))
val wordCounts = textRDD.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
wordCounts.collect()
# Python Shell
pyspark --master local[4]
>>> text_rdd = sc.parallelize(["Hello Spark", "Spark is great"])
>>> word_counts = text_rdd.flatMap(lambda x: x.split(" ")).map(lambda x: (x, 1)).reduceByKey(lambda x, y: x + y)
>>> word_counts.collect()
程序调试技巧
1. 日志配置
// 设置不同的日志级别
spark.sparkContext.setLogLevel("DEBUG") // 详细调试信息
spark.sparkContext.setLogLevel("INFO") // 一般信息
spark.sparkContext.setLogLevel("WARN") // 警告信息
spark.sparkContext.setLogLevel("ERROR") // 错误信息
2. 数据检查
// 检查数据分区情况
val rdd = spark.sparkContext.parallelize(data)
println(s"分区数: ${rdd.getNumPartitions}")
println(s"每个分区的数据量: ${rdd.glom().map(_.length).collect().mkString(", ")}")
// 采样数据
val sample = rdd.sample(false, 0.1).collect()
sample.foreach(println)
3. 性能监控
# 监控执行时间
import time
start_time = time.time()
# 执行Spark操作
result = rdd.count()
end_time = time.time()
print(f"执行时间: {end_time - start_time:.2f} 秒")
# 查看执行计划
df.explain(True)
常见错误和解决方案
1. 序列化错误
// 错误示例
class MyClass {
def process(rdd: RDD[String]): RDD[String] = {
rdd.map(this.transform) // 这会导致序列化错误
}
def transform(s: String): String = s.toUpperCase
}
// 正确做法
class MyClass extends Serializable { // 实现Serializable接口
def process(rdd: RDD[String]): RDD[String] = {
rdd.map(transform) // 或者使用静态方法
}
def transform(s: String): String = s.toUpperCase
}
2. 内存不足
# 增加驱动程序内存
spark-submit --driver-memory 4g --executor-memory 8g MyApp.jar
# 以下为Scala配置片段;客户端模式下,spark.driver.memory不能在应用代码中调整已启动Driver的堆大小,应使用启动参数。spark.executor.memory也应在创建SparkContext前配置。
val conf = new SparkConf()
.set("spark.driver.memory", "4g")
.set("spark.executor.memory", "8g")
3. 分区过多或过少
// 控制分区数
val rdd = spark.sparkContext.textFile("file.txt", 4) // 指定4个分区
// 重新分区
val repartitioned = rdd.repartition(8) // 增加分区
val coalesced = rdd.coalesce(2) // 减少分区
性能优化建议
1. 数据本地性
// 缓存频繁使用的数据
val frequentlyUsedRDD = rdd.filter(...).cache()
// 使用persist指定存储级别
import org.apache.spark.storage.StorageLevel
rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)
2. 避免Shuffle操作
// 不好的做法
val result = rdd.groupByKey().mapValues(_.sum)
// 更好的做法
val result = rdd.reduceByKey(_ + _)
3. 广播变量
// 使用广播变量避免重复传输大数据
val broadcastVar = spark.sparkContext.broadcast(largeMap)
val result = rdd.map { x =>
val map = broadcastVar.value
// 使用广播的数据
map.get(x.key)
}
扩展练习
练习1:文件处理增强版
def enhanced_word_count(spark, file_path):
"""增强版文件词频统计"""
# 读取文件
text_df = spark.read.text(file_path)
# 复杂的文本处理
result = text_df \
.select(explode(split(lower(regexp_replace(col("value"), "[^a-zA-Z\\s]", "")), "\\s+")).alias("word")) \
.filter(col("word") != "") \
.filter(length(col("word")) > 2) \
.groupBy("word") \
.count() \
.orderBy(desc("count"))
return result
# 使用示例
result_df = enhanced_word_count(spark, "hdfs://path/to/large/text/file")
result_df.show(20)
练习2:实时数据处理
// 模拟流数据处理
import org.apache.spark.sql.streaming.Trigger
val streamingDF = spark
.readStream
.format("socket")
.option("host", "localhost")
.option("port", 9999)
.load()
val wordCounts = streamingDF
.select(explode(split(col("value"), " ")).alias("word"))
.groupBy("word")
.count()
val query = wordCounts
.writeStream
.outputMode("complete")
.format("console")
.trigger(Trigger.ProcessingTime("10 seconds"))
.start()
query.awaitTermination()
相关链接
tip 学习建议
建议按照Scala → Python → Java的顺序学习,每种语言都要亲自运行代码,理解不同API的特点和使用场景。