Spark RDD 编程从驱动程序到共享变量、Shuffle 与持久化

1. Spark 应用的基本形态

  • Driver(驱动程序) :运行你的 main 函数,负责构建 DAG、提交任务、汇总结果。
  • Executors(执行器):分布在集群各节点,执行并行任务。
  • RDD(Resilient Distributed Dataset) :Spark 的核心抽象------可并行操作、具容错能力的分布式数据集。RDD 的每个分区在不同节点处理。

获取 RDD 的两条路径:

  1. 从外部存储读取(HDFS、本地、S3、HBase、任意 Hadoop InputFormat)
  2. 从 Driver 的本地集合并行化(sc.parallelize

容错性 :RDD 基于 lineage(血缘)自动从失败节点恢复。
持久化:可将 RDD 缓存在内存或磁盘,重复使用时显著加速。

2. 共享变量:Broadcast 与 Accumulator

  • 默认行为 :并行任务会收到"闭包"的拷贝,各 Executor 内部修改对 Driver 不可见。
  • Broadcast(广播变量) :把只读的大对象高效分发并缓存到各节点。
  • Accumulator(累加器)只支持加法 (交换结合),用于计数/求和等全局聚合;仅 Driver 可读值

示例(Python):

python 复制代码
# Broadcast
bvar = sc.broadcast({"threshold": 10})
rdd.map(lambda x: x > bvar.value["threshold"]).count()

# Accumulator
acc = sc.accumulator(0)
sc.parallelize([1,2,3,4]).foreach(lambda x: acc.add(x))
print(acc.value)  # 10

生产建议:广播对象创建后不要再修改 ;累加器的更新应在 action 内进行(在 transformation 中可能被重复执行)。

3. 语言与环境:如何链接 Spark

  • PySpark 版本 :Spark 4.0.1 适配 Python 3.9+,支持 CPython / PyPy 7.3.6+。

  • 两种运行方式

    • bin/spark-submit / bin/pyspark(无需 pip 安装 PySpark)
    • pip install pyspark 后,直接 python your_app.py 或继续用 spark-submit
  • 在项目里声明依赖(示例)

    python 复制代码
    install_requires = ["pyspark==4.0.1"]
  • 选择 Python 解释器

    bash 复制代码
    PYSPARK_PYTHON=python3.10 bin/pyspark
    PYSPARK_PYTHON=/path/to/pypy bin/spark-submit app.py

4. 初始化 Spark:SparkConf / SparkContext

python 复制代码
from pyspark import SparkConf, SparkContext

conf = SparkConf().setAppName("MyApp").setMaster("local[4]")  # 本地4核
sc = SparkContext(conf=conf)

生产集群 中常用 spark-submit --master ... 传入 master,不在代码里硬编码。

5. 用 Shell 快速实验(强烈推荐)

  • 进入交互式 Shell

    bash 复制代码
    ./bin/pyspark --master "local[4]" \
      --py-files code.py \
      --packages groupId:artifactId:version \
      --repositories https://repo1.maven.org/maven2
  • IPython / Jupyter

    bash 复制代码
    PYSPARK_DRIVER_PYTHON=ipython ./bin/pyspark
    
    PYSPARK_DRIVER_PYTHON=jupyter \
    PYSPARK_DRIVER_PYTHON_OPTS=notebook \
    ./bin/pyspark

在 Notebook 里可用 %pylab inline 做内嵌可视化。

6. 创建 RDD:并行化 vs. 外部数据集

6.1 并行化本地集合

python 复制代码
data = [1, 2, 3, 4, 5]
dist = sc.parallelize(data, numSlices=4)
print(dist.reduce(lambda a,b: a+b))  # 15

分区数经验值:每个 CPU 2~4 份。

6.2 外部数据集(Text / WholeText / SequenceFile / 任意 InputFormat)

python 复制代码
# 行式文本
distFile = sc.textFile("hdfs:///data/input/*.txt")
total = distFile.map(lambda s: len(s)).reduce(lambda a,b: a+b)

# small files -> (filename, content)
pairs = sc.wholeTextFiles("s3a://bucket/path/")

# SequenceFile(KV)
rdd = sc.parallelize(range(1,4)).map(lambda x: (x, "a"*x))
rdd.saveAsSequenceFile("path/to/file")
print(sorted(sc.sequenceFile("path/to/file").collect()))
# [(1,'a'), (2,'aa'), (3,'aaa')]

# 自定义 InputFormat(例:Elasticsearch)
# ./bin/pyspark --jars elasticsearch-hadoop.jar
conf = {"es.resource": "index/type"}
es_rdd = sc.newAPIHadoopRDD(
    "org.elasticsearch.hadoop.mr.EsInputFormat",
    "org.apache.hadoop.io.NullWritable",
    "org.elasticsearch.hadoop.mr.LinkedMapWritable",
    conf=conf
)
print(es_rdd.first())  # => Python dict

读取本地路径时需确保 worker 也能访问;通配与压缩文件开箱即用。

7. RDD 编程模型:转换(Transformations)与行动(Actions)

  • Lazy:转换是延迟的,仅在遇到行动时触发计算。

  • 常用转换(部分):

    • map / flatMap / filter
    • mapPartitions / mapPartitionsWithIndex
    • union / intersection / distinct
    • groupByKey(聚合推荐 reduceByKey/aggregateByKey
    • reduceByKey / aggregateByKey
    • join / leftOuterJoin / cogroup
    • repartition / coalesce / repartitionAndSortWithinPartitions
  • 常用行动(部分):

    • reduce / collect / count / first / take / takeSample
    • takeOrdered / saveAsTextFile / countByKey / foreach

示例:

python 复制代码
lines = sc.textFile("data.txt")
lineLens = lines.map(lambda s: len(s))     # 转换
total    = lineLens.reduce(lambda a,b: a+b) # 行动

复用 :如果 lineLens 后续还会多次使用,建议先 lineLens.persist()

8. 闭包与副作用:为什么"直接改全局变量"会翻车

python 复制代码
counter = 0
rdd = sc.parallelize([1,2,3])

def inc(x):
    global counter
    counter += x

# 错误示范:Executor 改的是闭包副本,Driver 看不到
rdd.foreach(inc)
print(counter)  # 仍可能是 0(分布式下)
  • 在分布式模式,任务执行前闭包会被序列化拷贝到各 Executor;修改发生在副本上。
  • 正确做法 :使用 Accumulator 聚合,或把需要的"配置"提前做成 Broadcast

打印元素 :想在 Driver 打印,用 take/collect 后在 Driver 打印;不要在 Executor 里 print 期待回到 Driver。

python 复制代码
for x in rdd.take(100):
    print(x)

9. Shuffle:最昂贵但不可避免的重分布

触发场景reduceByKey/aggregateByKey/groupByKey/join/repartition/coalesce(扩大分区) 等。
开销来源 :网络传输、磁盘 I/O、序列化/反序列化、堆上数据结构(溢出后落盘)。
内部机制 :map 端写按目标分区排序的中间文件;reduce 端按需拉取并聚合。
排序与顺序 :Shuffle 后分区集合与分区顺序可确定 ,但分区内元素顺序不保证;需要有序请用:

  • mapPartitions(...) 内排序
  • repartitionAndSortWithinPartitions(...)
  • sortBy(...) 进行全局排序

调优提示

  • 适度提高并行度(spark.sql.shuffle.partitions / rdd.repartition(n)
  • 避免滥用 groupByKey,优先 reduceByKey/aggregateByKey
  • 监控临时目录磁盘(spark.local.dir),长作业注意中间文件清理时机

10. 持久化(缓存)策略

python 复制代码
rdd.cache()  # = persist(StorageLevel.MEMORY_ONLY)
# or
from pyspark.storagelevel import StorageLevel
rdd.persist(StorageLevel.MEMORY_AND_DISK)

Python 存储级别: MEMORY_ONLY[_2]MEMORY_AND_DISK[_2]DISK_ONLY[_2|_3] 等;对象会通过 Pickle 序列化。
选择指南

  1. 能放下就用 MEMORY_ONLY(CPU 最省)
  2. 放不下 → MEMORY_ONLY_SER/MEMORY_AND_DISK(Scala/Java 下 SER 更省内存;PySpark天然序列化)
  3. 不轻易"溢出到磁盘",除非重算成本更高
  4. Web 服务低延迟 + 快速容错 → _2 级别做副本
  5. 释放缓存:rdd.unpersist(blocking=True)

11. 用 Key-Value RDD 做聚合与 Join(PySpark)

python 复制代码
lines  = sc.textFile("data.txt")
pairs  = lines.map(lambda s: (s, 1))
counts = pairs.reduceByKey(lambda a,b: a+b)
topN   = counts.sortBy(lambda kv: kv[1], ascending=False).take(10)

12. 部署到集群与作业启动

  • 打包 :Scala/Java → JAR;Python → .py/.zip(依赖走 --py-files 或集群镜像)
  • 提交bin/spark-submit 可投递到 Standalone / YARN / Kubernetes
  • 嵌入式启动 (Java/Scala):org.apache.spark.launcher 提供子进程 API

13. 单元测试友好

  • 测试中用 local[*] 创建 SparkContext
  • tearDownfinallysc.stop(),确保同一进程只存在一个 SparkContext

14. 实用清单(Troubleshooting & Best Practices)

  • 闭包陷阱:不要在 Executor 修改 Driver 变量;用 Accumulator。
  • Driver OOM :避免对大 RDD collect();先 take/sample/saveAsTextFile
  • 分区数 :Map 阶段/Shuffle 后适度提升并行度;小结果写出可 coalesce(1)
  • 数据路径 :本地/共享/分布式路径要一致可见;S3/HDFS 用 s3a:///hdfs://
  • 依赖管理 :第三方连接器走 --packages--jars,Python 依赖用 --py-files/镜像。
  • 热数据缓存:确实"常用再缓存",配合 WebUI 观察命中与内存占用。
相关推荐
VXHAruanjian8883 小时前
以智促效,释放创新力量,RPA助力企业全面自动化变革
大数据·人工智能
哦你看看4 小时前
Elasticsearch+Logstash+Filebeat+Kibana部署[7.17.3版本]
大数据·elasticsearch·搜索引擎
小鹿学程序6 小时前
搭建hadoop集群
大数据·hadoop·分布式
web3.08889996 小时前
淘宝(全量)商品详情 API 的分布式请求调用实践
分布式
lijun_xiao20096 小时前
SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式-学习笔记-1
分布式·spring cloud·rabbitmq
ApacheSeaTunnel6 小时前
LLM 时代,DataAgent × WhaleTunnel 如何将数据库变更瞬时 “转译” 为洞察?
大数据·ai·开源·llm·数据同步·白鲸开源·whaletunnel
二宝1527 小时前
黑马商城day8-ES01
分布式·微服务·架构
凯子坚持 c7 小时前
从零开始:开发一个仓颉三方库的完整实战
大数据·elasticsearch·搜索引擎
shepherd1267 小时前
破局延时任务(下):Spring Boot + DelayQueue 优雅实现分布式延时队列(实战篇)
java·spring boot·分布式