2. Spark环境搭建与数据源接入:集群模式选择、读取Hive/MySQL/Kafka数据源配置

在风控特征工程中,Spark环境的选择直接决定了特征计算的吞吐量与延迟。本章将聚焦于Spark集群模式的选型对比,以及三种核心数据源(Hive、MySQL、Kafka)的接入配置与调优要点。

2.1 Spark集群模式选择

风控场景通常面临高吞吐、低延迟、资源隔离三大挑战。下表对比了四种常见部署模式在风控场景下的适用性:

模式 资源管理 适用场景 风控推荐度
Local 单机多线程 开发调试、小样本验证 ⭐(仅开发)
Standalone Spark内置Master/Worker 中小规模离线批处理 ⭐⭐
YARN Hadoop YARN 企业级离线/近线特征生产 ⭐⭐⭐⭐⭐
Kubernetes K8s容器编排 实时特征、弹性伸缩、云原生 ⭐⭐⭐⭐
生产建议: 大多数风控团队选择 YARN 模式,因为它与Hive/HDFS深度集成,且支持动态资源分配与队列隔离,适合多租户特征生产环境。

以下为YARN模式下提交Spark作业的典型配置示例:

spark-submit \
  --master yarn \
  --deploy-mode cluster \
  --executor-memory 8g \
  --num-executors 20 \
  --executor-cores 4 \
  --conf spark.dynamicAllocation.enabled=true \
  --conf spark.shuffle.service.enabled=true \
  --class com.risk.feature.FeaturePipeline \
  feature-pipeline.jar

2.2 读取Hive数据源配置

Hive是风控特征分层中存储宽表与中间层特征的核心仓库。Spark读取Hive表时,需注意元数据同步与分区剪枝优化。

2.2.1 基础配置

from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("HiveFeatureReader") \
    .config("hive.metastore.uris", "thrift://metastore-host:9083") \
    .config("spark.sql.warehouse.dir", "/user/hive/warehouse") \
    .config("spark.sql.hive.convertMetastoreParquet", "true") \
    .enableHiveSupport() \
    .getOrCreate()

# 读取Hive分区表
df = spark.sql("""
    SELECT 
        user_id,
        feature_1,
        feature_2,
        dt
    FROM risk_dw.feature_wide_table
    WHERE dt = '2025-03-01'
""")

2.2.2 分区剪枝与谓词下推

风控特征表通常按天分区,必须确保Spark能利用分区过滤。以下配置可显著提升扫描效率:

参数 推荐值 说明
spark.sql.hive.metastorePartitionPruning true 启用元数据分区剪枝
spark.sql.adaptive.coalescePartitions.enabled true 动态合并小分区
spark.sql.hive.convertMetastoreParquet true 将Hive SerDe表转为Spark原生Parquet读取
经验: 在读取Hive宽表时,尽量只select需要的特征列,避免select * 导致大量列式存储的IO浪费。

2.3 读取MySQL数据源配置

MySQL常用于存储风控规则配置、黑名单、设备指纹等维度数据。Spark通过JDBC连接MySQL,需注意连接池与并发读取策略。

2.3.1 JDBC连接配置

df_mysql = spark.read \
    .format("jdbc") \
    .option("url", "jdbc:mysql://mysql-host:3306/risk_db?useSSL=false&serverTimezone=Asia/Shanghai") \
    .option("dbtable", "blacklist_device") \
    .option("user", "risk_user") \
    .option("password", "encrypted_password") \
    .option("driver", "com.mysql.cj.jdbc.Driver") \
    .option("numPartitions", "8") \
    .option("partitionColumn", "id") \
    .option("lowerBound", "1") \
    .option("upperBound", "10000000") \
    .load()

2.3.2 并发读取与连接池调优

参数 推荐值 作用
numPartitions 8~16 控制并发读取的JDBC连接数
partitionColumn 自增主键 用于数据切分的列
fetchSize 10000 每次拉取的行数,减少网络往返
connectionTimeout 30000 连接超时时间(毫秒)
注意: 不要将MySQL作为大规模特征存储使用。建议只读取维度小表(百万级以内),大表特征应提前同步至Hive或HBase。

2.4 读取Kafka数据源配置

实时风控场景下,Kafka是流式特征接入的标配。Spark Structured Streaming 提供了exactly-once语义与背压机制。

2.4.1 结构化流读取Kafka

df_kafka = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "kafka-broker1:9092,kafka-broker2:9092") \
    .option("subscribe", "risk_feature_input") \
    .option("startingOffsets", "latest") \
    .option("maxOffsetsPerTrigger", "50000") \
    .option("failOnDataLoss", "false") \
    .load()

# 解析Kafka消息(假设value为JSON格式)
from pyspark.sql.functions import from_json, col, schema_of_json

json_schema = schema_of_json("""{"user_id":"","event_type":"","amount":0.0,"ts":""}""")
df_parsed = df_kafka \
    .select(from_json(col("value").cast("string"), json_schema).alias("data")) \
    .select("data.*")

2.4.2 关键参数说明

参数 推荐值 说明
maxOffsetsPerTrigger 50000~200000 控制每个trigger处理的最大消息数,防止背压
startingOffsets latest / earliest 实时场景用latest,回溯用earliest
failOnDataLoss false 避免Kafka topic删除导致作业失败
kafka.group.id 自定义 用于offset管理,建议按作业命名

2.5 数据源接入架构总览

下图展示了风控特征分层中三种数据源的接入位置与数据流向:

风控特征数据源接入架构 风控特征数据源接入架构 Hive 离线宽表 批量特征 / 分区表 MySQL 维度表 黑名单 / 规则配置 Kafka 实时流 事件流 / 实时特征 Spark 特征计算引擎 批处理 / 微批处理 / 结构化流 特征分层:原始层 → 衍生层 → 聚合层 特征输出 Hive 特征宽表 / Redis 实时特征 / 模型服务 💡 接入策略: Hive用于T+1批量特征;MySQL用于维度关联;Kafka用于实时事件特征(毫秒级延迟)。

2.6 多数据源联合读取示例

在实际风控特征生产中,经常需要将Hive、MySQL、Kafka三路数据做关联。以下代码展示了如何在一个Pipeline中完成联合读取:

# 1. 读取Hive离线特征
df_hive = spark.sql("SELECT user_id, feature_a, feature_b FROM risk_dw.feature_wide WHERE dt='2025-03-01'")

# 2. 读取MySQL黑名单维度
df_blacklist = spark.read \
    .format("jdbc") \
    .option("url", "jdbc:mysql://mysql-host:3306/risk_db") \
    .option("dbtable", "blacklist_device") \
    .option("user", "risk_user") \
    .option("password", "encrypted_pwd") \
    .option("numPartitions", "4") \
    .option("partitionColumn", "id") \
    .option("lowerBound", "1") \
    .option("upperBound", "5000000") \
    .load()

# 3. 读取Kafka实时事件流
df_kafka_stream = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "kafka-broker:9092") \
    .option("subscribe", "risk_event_input") \
    .option("maxOffsetsPerTrigger", "100000") \
    .load() \
    .selectExpr("CAST(value AS STRING) as json_str") \
    .select(from_json("json_str", event_schema).alias("event")) \
    .select("event.*")

# 4. 关联:离线特征 + 维度表(批处理)
df_joined_batch = df_hive.join(df_blacklist, on="user_id", how="left")

# 5. 关联:实时流 + 维度表(流-维关联)
df_joined_stream = df_kafka_stream \
    .join(df_blacklist, on="device_id", how="left")
最佳实践: 对于流-维关联,建议将MySQL维度表缓存为广播变量(spark.sql.autoBroadcastJoinThreshold 调大),避免频繁的JDBC查询。

2.7 本章小结

  • 集群模式:生产环境首选YARN,兼顾资源隔离与动态分配;K8s适合云原生实时场景。
  • Hive接入:利用分区剪枝与谓词下推,避免全表扫描;只select必要列。
  • MySQL接入:通过JDBC并发读取,控制numPartitions与fetchSize;仅用于维度小表。
  • Kafka接入:使用Structured Streaming,配置maxOffsetsPerTrigger实现背压控制。
  • 联合读取:批流一体架构中,合理选择关联策略(广播join vs 普通join)。