3. Spark批处理基础:核心概念与作业提交流程
在风控特征工程中,每日需处理数亿至数十亿条用户行为日志、交易流水与外部数据。Spark作为分布式批处理引擎,是特征分层加工的核心计算平台。本章聚焦Spark RDD与DataFrame两大抽象、SparkSession的初始化规范以及批处理作业的完整提交流程,为后续特征计算Pipeline打下基础。
3.1 Spark核心抽象:RDD与DataFrame
| 特性 | RDD (Resilient Distributed Dataset) | DataFrame |
|---|---|---|
| 本质 | 不可变、分区的分布式对象集合 | 带Schema的分布式行集合,类似关系表 |
| 数据表示 | Java/Python对象,无内置Schema | Row对象 + StructType Schema |
| 优化能力 | 依赖开发者手动优化(如使用mapPartitions) |
Catalyst优化器自动执行谓词下推、列剪枝、常量折叠 |
| 序列化 | Java/Kryo序列化,开销较大 | Tungsten二进制格式,堆外内存管理,性能高 |
| API风格 | 函数式(map, filter, reduceByKey) | 声明式(select, where, groupBy) + SQL |
| 适用场景 | 非结构化数据、自定义复杂逻辑、需要细粒度控制 | 结构化/半结构化数据、SQL分析、特征加工 |
| 风控场景推荐 | 原始日志解析、自定义UDF无法覆盖的复杂转换 | 特征聚合、多表Join、窗口计算、特征宽表生成 |
选型建议: 风控特征工程中,90%以上的批处理任务应优先使用DataFrame API。仅在需要手动控制分区内迭代逻辑(如复杂状态计算)或使用第三方非结构化库时,才回退到RDD。
3.2 SparkSession初始化规范
SparkSession是Spark 2.0+的统一入口,替代了旧的SparkContext、SQLContext、HiveContext。在风控生产环境中,初始化需考虑资源配置、Hive集成与动态分配。
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("risk_feature_batch") \
.config("spark.sql.adaptive.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.minPartitionNum", "200") \
.config("spark.sql.shuffle.partitions", "400") \
.config("spark.dynamicAllocation.enabled", "true") \
.config("spark.dynamicAllocation.minExecutors", "10") \
.config("spark.dynamicAllocation.maxExecutors", "200") \
.config("spark.sql.warehouse.dir", "/user/hive/warehouse") \
.enableHiveSupport() \
.getOrCreate()
| 配置项 | 推荐值 | 说明 |
|---|---|---|
spark.sql.adaptive.enabled |
true | 开启自适应查询执行,自动合并小分区、调整Join策略 |
spark.sql.shuffle.partitions |
200~400 | Shuffle默认分区数,根据数据量调整,避免小文件过多 |
spark.dynamicAllocation.enabled |
true | 动态资源分配,避免空闲Executor浪费集群资源 |
spark.sql.warehouse.dir |
Hive仓库路径 | 指定Hive表存储位置,需与Hive Metastore一致 |
生产注意: 不要在循环或函数内部重复创建SparkSession。一个Spark Application内应全局共享一个SparkSession实例,否则会导致JVM资源泄漏和Driver OOM。
3.3 批处理作业提交流程
一个完整的Spark批处理作业从提交到完成,经历以下阶段:
- 客户端提交:使用
spark-submit脚本,指定主类、部署模式、资源配置与依赖JAR/Py文件。 - Driver启动:在集群的Gateway节点(client模式)或YARN ApplicationMaster(cluster模式)上启动Driver进程。
- SparkContext初始化:Driver创建SparkContext,向Cluster Manager(YARN/K8s)申请资源。
- Executor分配:Cluster Manager在Worker节点上启动Executor进程,并向Driver注册。
- 作业调度:Driver将用户代码转换为DAG(有向无环图),由DAGScheduler拆分为Stage,TaskScheduler将Task分发到Executor。
- 任务执行:Executor并行执行Task,数据在Shuffle阶段跨节点传输。
- 结果收集与清理:Driver收集最终结果(如保存到Hive表),释放所有Executor资源。
风控批处理提交流程示例:
spark-submit \
--master yarn \
--deploy-mode cluster \
--queue risk_priority \
--num-executors 100 \
--executor-cores 4 \
--executor-memory 8g \
--driver-memory 4g \
--conf spark.sql.adaptive.enabled=true \
--py-files feature_lib.zip,config.py \
risk_feature_pipeline.py \
--date 2025-03-01 \
--feature_type rolling_30d
3.4 核心流程SVG图
3.5 RDD与DataFrame血缘关系SVG图
3.6 风控批处理最佳实践
| 实践项 | 具体做法 | 收益 |
|---|---|---|
| 分区策略 | 按日期分区(dt字段),每个分区数据量控制在128MB~1GB |
避免小文件过多,提升读取并行度 |
| 列剪枝 | 只select特征计算所需的列,避免select * | 减少I/O与网络传输量30%~70% |
| 谓词下推 | 在读取时就过滤无效数据(如where dt='2025-03-01') |
减少Shuffle数据量,加速计算 |
| 广播变量 | 将维度表(如黑名单、设备指纹库)广播到每个Executor | 避免大表Join的Shuffle开销 |
| 缓存策略 | 对多次使用的中间结果使用.cache()或.persist(StorageLevel.MEMORY_AND_DISK) |
避免重复计算,缩短DAG执行时间 |
| Shuffle调优 | 设置spark.sql.adaptive.coalescePartitions.enabled=true自动合并小分区 |
减少Shuffle文件数,降低Driver压力 |
风控特征批处理典型Pipeline:
# 1. 读取原始日志(按分区过滤)
raw_df = spark.table("ods.user_behavior_log") \
.where(col("dt") == batch_date)
# 2. 特征加工(窗口聚合)
feature_df = raw_df.groupBy("user_id") \
.agg(
count("*").alias("total_actions_30d"),
sum(when(col("action_type") == "login", 1).otherwise(0)).alias("login_cnt_30d"),
avg(col("amount")).alias("avg_amount_30d")
)
# 3. 关联维度表(广播)
dim_df = spark.table("dim.user_risk_level").hint("broadcast")
result_df = feature_df.join(dim_df, "user_id", "left")
# 4. 写入特征宽表
result_df.write \
.mode("overwrite") \
.partitionBy("dt") \
.format("parquet") \
.saveAsTable("feature.user_rolling_features")
生产监控指标: 每次批处理作业完成后,记录
spark.sql.shuffle.partitions实际Shuffle数据量、Stage耗时、GC时间与Executor峰值内存。这些指标是后续调优与资源预算的核心依据。