未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合已有Scala与Spark基础的读者学习ETL编排、交通流分析及数据质量规则引擎的组织方式,不是可直接部署或完整复现的企业系统;本次仅作正文结构与代码静态审阅,未运行示例或进行外部事实核验。复现需自行准备电商CSV、订单与客户DataFrame、Kafka消息及兼容的Spark Kafka连接器和Delta运行环境,交通生成器仅返回内存列表。当前仍有流式countDistinct不受支持、综合评分未归一化、ETL缺陷重复扣分、空数据集比率未处理、众数填充不可靠,以及getWorstCategories占位符表达式的编译问题,不宜直接据此判断生产数据健康状况或执行自动修复。用户分层、固定会话时长和交通阈值仅为教学假设,failOnDataLoss=false不保证数据完整性;告警字段映射、ConsistencyRule命名冲突、有效性规则对null表达式的处理及相关注释已修正,不再作为现存风险。

Scala在数据开发中的应用 - 从理论到实战的完整指南

abstract 📋 本节概览

  • 🎯 学习目标:掌握Scala在企业级数据开发中的实际应用
  • 📚 前置知识:Scala集合框架与模式匹配
  • ⏱️ 预计用时:180分钟

🏗️ 为什么Scala是数据开发的黄金搭档?

想象一下,你要建造一座摩天大楼。传统的编程语言就像普通的建筑工具,而Scala就是一套精密的工程装备——既有Java的稳固基础,又有函数式编程的优雅设计。在数据开发的世界里,Scala就是这样一个完美的工具。

tip 💡 核心优势

Scala凭借其独特的语言特性,在数据开发领域展现出强大优势:

  • 类型安全:编译时就能发现数据结构错误
  • 函数式编程:数据转换管道式处理
  • JVM生态:无缝集成Java大数据生态
  • 并发处理:天生支持高并发数据处理

🎯 Scala数据开发生态全景

%%{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; }"}}%%
flowchart TB
    subgraph "Scala数据开发生态系统"
        A[Apache Spark<br/>🔥 分布式计算引擎]
        B[批处理<br/>📊 大批量数据处理]
        C[流处理<br/>🌊 实时数据处理]
        D[机器学习<br/>🤖 MLlib算法库]
        
        E[Akka<br/>⚡ 并发处理框架]
        F[分布式系统<br/>🏢 集群计算]
        G[流式计算<br/>📈 实时分析]
        H[响应式架构<br/>🔄 事件驱动]
        
        I[Kafka Streams<br/>📨 消息流处理]
        J[事件驱动<br/>📬 异步消息处理]
        K[数据管道<br/>🚰 ETL流水线]
        
        L[其他工具<br/>🔧 辅助组件]
        M[Slick<br/>💾 数据库访问]
        N[Cats<br/>🐱 函数式编程]
    end
    
    A --> B
    A --> C
    A --> D
    E --> F
    E --> G
    E --> H
    I --> J
    I --> K
    L --> M
    L --> N
    
    style A fill:#ff6b6b,color:#fff
    style E fill:#4ecdc4,color:#fff
    style I fill:#45b7d1,color:#fff

🔧 场景一:企业级ETL数据管道

数据ETL就像一座现代化的食品加工厂。原材料(原始数据)从各个供应商(数据源)送来,经过清洗、加工、包装(转换),最终成为可以直接使用的产品(清洁数据)。

important 🏭 ETL核心理念

ETL不仅是数据搬运,更是数据价值提升的过程:

  • Extract:从多源头精准提取
  • Transform:按业务规则智能转换
  • Load:高效装载到目标系统

💼 实战案例:电商数据处理系统

import org.apache.spark.sql.{SparkSession, DataFrame}
import org.apache.spark.sql.functions._
import java.time.LocalDateTime

// 数据模型定义 - 就像设计产品规格
case class RawUserEvent(
  userId: Long,
  eventType: String,
  timestamp: String,
  productId: Option[Long],
  amount: Option[Double],
  sessionId: String
)

case class CleanUserEvent(
  userId: Long,
  eventType: EventType,
  timestamp: java.sql.Timestamp,
  productId: Option[Long],
  amount: Option[Double],
  sessionId: String,
  processed_at: java.sql.Timestamp
)

sealed trait EventType
case object Login extends EventType
case object Purchase extends EventType  
case object Browse extends EventType

class EcommerceETLPipeline {
  
  // Spark会话配置 - 就像配置工厂的生产线
  implicit val spark: SparkSession = SparkSession.builder()
    .appName("Ecommerce ETL Pipeline")
    .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    .config("spark.sql.adaptive.enabled", "true")
    .config("spark.sql.adaptive.coalescePartitions.enabled", "true")
    .getOrCreate()
  
  import spark.implicits._
  
  // 数据提取 - 从各个仓库收集原料
  def extractData(inputPath: String): DataFrame = {
    val rawData = spark.read
      .option("header", "true")
      .option("inferSchema", "true")  
      .option("timestampFormat", "yyyy-MM-dd HH:mm:ss")
      .csv(inputPath)
    
    println(s"✅ 成功提取 ${rawData.count()} 条原始记录")
    
    // 添加处理时间戳
    rawData.withColumn("extract_time", current_timestamp())
  }
  
  // 数据转换 - 核心的加工处理流程
  def transformData(rawData: DataFrame): DataFrame = {
    
    // 数据清洗:就像去除有害物质
    val cleanedData = rawData
      .filter($"userId".isNotNull && $"userId" > 0)           // 过滤无效用户ID
      .filter($"eventType".isNotNull)                         // 确保事件类型存在
      .filter($"timestamp".isNotNull)                         // 确保时间戳存在
      .dropDuplicates("userId", "eventType", "timestamp")     // 去除重复记录
    
    println(s"🧹 数据清洗完成,保留 ${cleanedData.count()} 条有效记录")
    
    // 数据标准化:就像按统一规格包装
    val standardizedData = cleanedData
      .withColumn("eventType_normalized", 
        when($"eventType" === "login", "LOGIN")
        .when($"eventType" === "purchase", "PURCHASE")
        .when($"eventType" === "browse", "BROWSE")
        .otherwise("UNKNOWN"))
      .withColumn("timestamp_parsed", 
        to_timestamp($"timestamp", "yyyy-MM-dd HH:mm:ss"))
      .withColumn("amount_clean", 
        when($"amount".isNull, 0.0).otherwise($"amount"))
    
    // 数据丰富化:就像添加营养成分
    val enrichedData = standardizedData
      .withColumn("hour_of_day", hour($"timestamp_parsed"))
      .withColumn("day_of_week", dayofweek($"timestamp_parsed"))
      .withColumn("user_segment",
        when($"amount_clean" > 1000, "高价值用户")
        .when($"amount_clean" > 100, "普通用户")
        .otherwise("新手用户"))
      .withColumn("session_duration_estimate",
        // 简单的会话时长估算
        when($"eventType_normalized" === "PURCHASE", 30)
        .when($"eventType_normalized" === "BROWSE", 5)  
        .otherwise(2))
    
    println(s"✨ 数据丰富化完成,添加了用户分段和时间特征")
    
    enrichedData
  }
  
  // 数据质量检查 - 质量控制部门
  def validateDataQuality(data: DataFrame): DataFrame = {
    val totalRecords = data.count()
    
    // 检查数据完整性
    val nullUserIds = data.filter($"userId".isNull).count()
    val nullTimestamps = data.filter($"timestamp_parsed".isNull).count()
    val invalidAmounts = data.filter($"amount_clean" < 0).count()
    
    // 计算质量分数
    val qualityScore = (totalRecords - nullUserIds - nullTimestamps - invalidAmounts).toDouble / totalRecords
    
    println(s"""
    |📊 数据质量报告:
    |总记录数: $totalRecords
    |空用户ID: $nullUserIds
    |空时间戳: $nullTimestamps  
    |无效金额: $invalidAmounts
    |质量分数: ${(qualityScore * 100).formatted("%.2f")}%
    """.stripMargin)
    
    if (qualityScore < 0.95) {
      throw new IllegalStateException(s"数据质量不达标: ${qualityScore * 100}% < 95%")
    }
    
    data
  }
  
  // 数据装载 - 产品包装和配送
  def loadData(processedData: DataFrame, outputPath: String): Unit = {
    // 按用户分段分区存储,提高查询效率
    processedData
      .coalesce(10)  // 控制输出文件数量,避免小文件问题
      .write
      .mode("overwrite")
      .partitionBy("user_segment", "day_of_week")  // 智能分区
      .parquet(outputPath)
    
    // 生成数据字典和统计信息
    val summary = processedData
      .groupBy("user_segment", "eventType_normalized")
      .agg(
        count("*").as("record_count"),
        avg("amount_clean").as("avg_amount"),
        max("timestamp_parsed").as("latest_activity")
      )
    
    summary.write
      .mode("overwrite")
      .json(s"$outputPath/summary")
    
    println(s"📦 数据成功装载到: $outputPath")
    println(s"📈 生成统计摘要: $outputPath/summary")
  }
  
  // 完整ETL流程编排
  def runPipeline(inputPath: String, outputPath: String): Unit = {
    try {
      println("🚀 开始ETL数据处理流程...")
      
      val startTime = System.currentTimeMillis()
      
      // Step 1: 提取
      val rawData = extractData(inputPath)
      
      // Step 2: 转换
      val transformedData = transformData(rawData)
      
      // Step 3: 验证
      val validatedData = validateDataQuality(transformedData)
      
      // Step 4: 装载
      loadData(validatedData, outputPath)
      
      val endTime = System.currentTimeMillis()
      val duration = (endTime - startTime) / 1000.0
      
      println(s"✅ ETL流程执行成功!用时: ${duration}秒")
      
    } catch {
      case ex: Exception =>
        println(s"❌ ETL流程执行失败: ${ex.getMessage}")
        ex.printStackTrace()
        throw ex
    }
  }
}

example 💼 使用示例

// 创建ETL管道实例
val etlPipeline = new EcommerceETLPipeline()

// 执行完整的ETL流程
etlPipeline.runPipeline(
  inputPath = "hdfs://namenode:9000/raw/user_events/",
  outputPath = "hdfs://namenode:9000/processed/user_events/"
)

⚡ 场景二:实时流式数据处理

实时数据流处理就像城市的交通管制系统。车辆(数据)持续不断地在道路上行驶,我们需要实时监控交通状况,及时发现拥堵(异常),并动态调整信号灯(处理策略)。

note 🌊 流处理核心概念

  • 无界数据流:数据源源不断地产生
  • 低延迟处理:毫秒到秒级的响应时间
  • 状态管理:维护历史信息进行分析
  • 容错恢复:系统故障后自动恢复

🚗 实战案例:智能交通监控系统

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.streaming.{OutputMode, Trigger}
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
import java.sql.Timestamp

// 交通数据模型
case class TrafficEvent(
  vehicleId: String,
  location: String,
  speed: Double,
  timestamp: Timestamp,
  eventType: String  // 进入、离开、违规等
)

case class TrafficAlert(
  location: String,
  alertType: String,
  severity: String,
  description: String,
  timestamp: Timestamp,
  affectedVehicles: Long
)

class TrafficStreamProcessor {
  
  implicit val spark: SparkSession = SparkSession.builder()
    .appName("Smart Traffic Monitoring")
    .config("spark.sql.streaming.checkpointLocation", "/tmp/traffic-checkpoint")
    .config("spark.sql.streaming.stateStore.maintenanceInterval", "60s")
    .getOrCreate()
  
  import spark.implicits._
  
  // 定义输入数据结构
  val trafficSchema = StructType(Array(
    StructField("vehicleId", StringType, false),
    StructField("location", StringType, false),
    StructField("speed", DoubleType, false),
    StructField("timestamp", TimestampType, false),
    StructField("eventType", StringType, false)
  ))
  
  def processTrafficStream(): Unit = {
    
    // 1. 从Kafka读取实时交通数据
    val rawTrafficStream = spark.readStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "localhost:9092")
      .option("subscribe", "traffic-events")
      .option("startingOffsets", "latest")
      .option("failOnDataLoss", "false")  // 示例:允许部分数据丢失情形下继续运行,不保证数据完整性
      .load()
    
    // 2. 解析JSON数据
    val trafficEvents = rawTrafficStream
      .select(from_json($"value".cast("string"), trafficSchema).as("data"))
      .select("data.*")
      .as[TrafficEvent]
    
    // 3. 实时交通流量分析
    val trafficFlowAnalysis = trafficEvents
      .withWatermark("timestamp", "30 seconds")  // 处理延迟数据
      .groupBy(
        window($"timestamp", "2 minutes", "30 seconds"),  // 2分钟窗口,30秒滑动
        $"location"
      )
      .agg(
        count("*").as("vehicle_count"),
        avg("speed").as("avg_speed"),
        min("speed").as("min_speed"),
        max("speed").as("max_speed"),
        countDistinct("vehicleId").as("unique_vehicles")
      )
      .withColumn("congestion_level", 
        when($"avg_speed" < 20, "严重拥堵")
        .when($"avg_speed" < 40, "轻度拥堵")
        .when($"avg_speed" < 60, "缓慢通行")
        .otherwise("通畅"))
      .withColumn("traffic_density", $"vehicle_count" / 2.0)  // 每分钟密度
    
    // 4. 异常检测和告警
    val anomalyDetection = trafficEvents
      .withWatermark("timestamp", "1 minute")
      .groupBy(
        window($"timestamp", "5 minutes"),
        $"location"
      )
      .agg(
        avg("speed").as("avg_speed"),
        stddev("speed").as("speed_variance"),
        count("*").as("event_count")
      )
      .filter(
        $"avg_speed" < 10 ||           // 平均速度过低
        $"speed_variance" > 50 ||     // 速度波动过大  
        $"event_count" > 1000         // 车辆数量异常
      )
      .select(
        $"location",
        lit("TRAFFIC_ANOMALY").as("alertType"),
        when($"avg_speed" < 10, "HIGH")
        .when($"speed_variance" > 50, "MEDIUM")
        .otherwise("LOW").as("severity"),
        concat(
          lit("检测到交通异常: 平均速度="), $"avg_speed".cast("string"),
          lit(", 速度标准差="), $"speed_variance".cast("string"),
          lit(", 车辆数="), $"event_count".cast("string")
        ).as("description"),
        current_timestamp().as("timestamp"),
        $"event_count".as("affectedVehicles")
      )
      .as[TrafficAlert]
    
    // 5. 违章检测
    val violationDetection = trafficEvents
      .filter($"speed" > 80)  // 假设限速80
      .withWatermark("timestamp", "10 seconds")
      .groupBy(
        window($"timestamp", "1 minute"),
        $"location"
      )
      .agg(
        count("*").as("violation_count"),
        collect_list("vehicleId").as("violating_vehicles")
      )
      .filter($"violation_count" > 0)
      .select(
        $"location",
        lit("SPEED_VIOLATION").as("alertType"),
        lit("HIGH").as("severity"),
        concat(
          lit("发现"), $"violation_count".cast("string"),
          lit("起超速违章")
        ).as("description"),
        current_timestamp().as("timestamp"),
        $"violation_count".as("affectedVehicles")
      )
      .as[TrafficAlert]
    
    // 6. 输出流配置
    
    // 实时流量监控输出到控制台
    val flowMonitorQuery = trafficFlowAnalysis.writeStream
      .outputMode(OutputMode.Update())
      .format("console")
      .option("truncate", false)
      .trigger(Trigger.ProcessingTime("30 seconds"))
      .queryName("traffic-flow-monitor")
      .start()
    
    // 告警信息发送到Kafka
    val anomalyAlertQuery = anomalyDetection
      .select(to_json(struct($"*")).as("value"))
      .writeStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "localhost:9092")
      .option("topic", "traffic-alerts")
      .option("checkpointLocation", "/tmp/anomaly-checkpoint")
      .trigger(Trigger.ProcessingTime("10 seconds"))
      .queryName("anomaly-alerts")
      .start()
    
    // 违章信息发送到执法部门
    val violationAlertQuery = violationDetection
      .select(to_json(struct($"*")).as("value"))
      .writeStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "localhost:9092")
      .option("topic", "violation-alerts")
      .option("checkpointLocation", "/tmp/violation-checkpoint")
      .trigger(Trigger.ProcessingTime("5 seconds"))
      .queryName("violation-alerts")
      .start()
    
    // 历史数据存储
    val historicalDataQuery = trafficEvents
      .writeStream
      .format("delta")  // 使用Delta Lake存储
      .option("path", "/data/traffic/historical")
      .option("checkpointLocation", "/tmp/historical-checkpoint")
      .partitionBy("location")
      .trigger(Trigger.ProcessingTime("2 minutes"))
      .queryName("historical-data")
      .start()
    
    println("🚦 智能交通监控系统已启动...")
    println("📊 实时流量分析...")
    println("🚨 异常检测和告警...")
    println("📝 违章检测...")
    
    // 优雅关闭
    sys.ShutdownHookThread {
      println("🛑 正在关闭交通监控系统...")
      flowMonitorQuery.stop()
      anomalyAlertQuery.stop()
      violationAlertQuery.stop()
      historicalDataQuery.stop()
      spark.stop()
    }
    
    // 等待查询完成
    flowMonitorQuery.awaitTermination()
  }
}

// 数据生成器(用于测试)
class TrafficDataGenerator {
  
  def generateSampleData(): List[TrafficEvent] = {
    val locations = List("路口A", "路口B", "路口C", "高速入口", "市中心")
    val random = scala.util.Random
    
    (1 to 1000).map { i =>
      TrafficEvent(
        vehicleId = f"CAR$i%04d",
        location = locations(random.nextInt(locations.length)),
        speed = 20 + random.nextGaussian() * 15,  // 正态分布的速度
        timestamp = new Timestamp(System.currentTimeMillis()),
        eventType = if (random.nextDouble() < 0.1) "VIOLATION" else "NORMAL"
      )
    }.toList
  }
}

warning ⚠️ 生产环境注意事项

  1. 检查点配置:确保检查点目录的高可用性
  2. 背压处理:监控流处理的延迟和吞吐量
  3. 内存管理:合理配置Spark Streaming的内存参数
  4. 故障恢复:测试各种故障场景的恢复能力

📊 场景三:数据质量监控体系

数据质量监控就像食品安全检测实验室。每一批进来的"食材"(数据)都要经过严格的检测,确保没有"有害物质"(错误数据),符合"营养标准"(业务规则),才能放心"上餐桌"(投入使用)。

🔬 智能数据质量检测框架

import org.apache.spark.sql.{DataFrame, SparkSession, Column}
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
import scala.collection.mutable.ListBuffer
import java.time.LocalDateTime

// 质量规则抽象
trait QualityRule {
  def name: String
  def description: String  
  def category: String
  def validate(df: DataFrame): QualityResult
  def autoFix(df: DataFrame): Option[DataFrame] = None  // 自动修复功能
}

// 质量检测结果
case class QualityResult(
  ruleName: String,
  category: String,
  passed: Boolean,
  severity: String,  // HIGH, MEDIUM, LOW
  message: String,
  affectedRows: Long,
  totalRows: Long,
  passRate: Double,
  recommendations: List[String] = List.empty
)

// 完整性检查规则
class CompletenessRule(columnName: String, threshold: Double = 1.0) extends QualityRule {
  override def name: String = s"Completeness_$columnName"
  override def description: String = s"检查列 '$columnName' 的完整性"
  override def category: String = "完整性"
  
  override def validate(df: DataFrame): QualityResult = {
    val totalRows = df.count()
    val nullRows = df.filter(col(columnName).isNull || col(columnName) === "").count()
    val completenessRate = (totalRows - nullRows).toDouble / totalRows
    
    val passed = completenessRate >= threshold
    val severity = if (completenessRate < 0.5) "HIGH"
                  else if (completenessRate < 0.8) "MEDIUM"  
                  else "LOW"
    
    QualityResult(
      ruleName = name,
      category = category,
      passed = passed,
      severity = severity,
      message = f"完整性: ${completenessRate * 100}%.2f%% (阈值: ${threshold * 100}%.1f%%)",
      affectedRows = nullRows,
      totalRows = totalRows,
      passRate = completenessRate,
      recommendations = if (!passed) List(
        s"建议检查数据源中 $columnName 字段的数据收集过程",
        "考虑添加默认值或数据修复逻辑"
      ) else List.empty
    )
  }
  
  override def autoFix(df: DataFrame): Option[DataFrame] = {
    // 简单的自动修复:用众数填充空值
    try {
      val mode = df.groupBy(columnName).count()
        .orderBy(desc("count"))
        .first()
        .getAs[String](columnName)
      
      Some(df.na.fill(mode, Array(columnName)))
    } catch {
      case _: Exception => None
    }
  }
}

// 唯一性检查规则
class UniquenessRule(columnNames: String*) extends QualityRule {
  override def name: String = s"Uniqueness_${columnNames.mkString("_")}"
  override def description: String = s"检查字段组合 [${columnNames.mkString(", ")}] 的唯一性"
  override def category: String = "唯一性"
  
  override def validate(df: DataFrame): QualityResult = {
    val totalRows = df.count()
    val distinctRows = df.select(columnNames.map(col): _*).distinct().count()
    val duplicateRows = totalRows - distinctRows
    val uniquenessRate = distinctRows.toDouble / totalRows
    
    val passed = duplicateRows == 0
    val severity = if (duplicateRows > totalRows * 0.1) "HIGH"
                  else if (duplicateRows > 0) "MEDIUM"
                  else "LOW"
    
    QualityResult(
      ruleName = name,
      category = category,
      passed = passed,
      severity = severity,
      message = s"发现 $duplicateRows 条重复记录,唯一性: ${(uniquenessRate * 100).formatted("%.2f")}%",
      affectedRows = duplicateRows,
      totalRows = totalRows,
      passRate = uniquenessRate,
      recommendations = if (!passed) List(
        "检查数据源是否存在重复数据插入",
        "考虑在ETL过程中添加去重逻辑",
        "建立唯一性约束或主键约束"
      ) else List.empty
    )
  }
  
  override def autoFix(df: DataFrame): Option[DataFrame] = {
    Some(df.dropDuplicates(columnNames))
  }
}

// 有效性检查规则
class ValidityRule(columnName: String, validationLogic: Column) extends QualityRule {
  override def name: String = s"Validity_$columnName"
  override def description: String = s"检查列 '$columnName' 的有效性"
  override def category: String = "有效性"
  
  override def validate(df: DataFrame): QualityResult = {
    val totalRows = df.count()
    val invalidRows = df.filter(!coalesce(validationLogic, lit(false))).count()
    val validityRate = (totalRows - invalidRows).toDouble / totalRows
    
    val passed = invalidRows == 0
    val severity = if (invalidRows > totalRows * 0.05) "HIGH"
                  else if (invalidRows > 0) "MEDIUM"
                  else "LOW"
    
    QualityResult(
      ruleName = name,
      category = category,
      passed = passed,
      severity = severity,
      message = s"发现 $invalidRows 条无效记录,有效性: ${(validityRate * 100).formatted("%.2f")}%",
      affectedRows = invalidRows,
      totalRows = totalRows,
      passRate = validityRate,
      recommendations = if (!passed) List(
        s"检查 $columnName 字段的输入验证规则",
        "加强数据输入时的格式验证",
        "建立数据清洗和标准化流程"
      ) else List.empty
    )
  }
}

// 一致性检查规则
class ConsistencyRule(
  ruleName: String,
  ruleDescription: String,
  checkLogic: DataFrame => (Long, Long)  // (违规记录数, 总记录数)
) extends QualityRule {
  override def name: String = ruleName
  override def description: String = ruleDescription
  override def category: String = "一致性"
  
  override def validate(df: DataFrame): QualityResult = {
    val (violationCount, totalCount) = checkLogic(df)
    val consistencyRate = (totalCount - violationCount).toDouble / totalCount
    
    val passed = violationCount == 0
    val severity = if (violationCount > totalCount * 0.02) "HIGH"
                  else if (violationCount > 0) "MEDIUM"
                  else "LOW"
    
    QualityResult(
      ruleName = name,
      category = category,
      passed = passed,
      severity = severity,
      message = s"发现 $violationCount 条不一致记录,一致性: ${(consistencyRate * 100).formatted("%.2f")}%",
      affectedRows = violationCount,
      totalRows = totalCount,
      passRate = consistencyRate,
      recommendations = if (!passed) List(
        "检查业务规则的实现逻辑",
        "确保跨系统数据的同步一致性",
        "建立数据一致性监控机制"
      ) else List.empty
    )
  }
}

// 数据质量监控引擎
class DataQualityEngine(spark: SparkSession) {
  
  import spark.implicits._
  
  private val rules = ListBuffer[QualityRule]()
  private val history = ListBuffer[QualityReport]()
  
  // 添加质量规则
  def addRule(rule: QualityRule): DataQualityEngine = {
    rules += rule
    this
  }
  
  def addRules(newRules: Seq[QualityRule]): DataQualityEngine = {
    rules ++= newRules
    this
  }
  
  // 执行全面质量检查
  def runQualityAssessment(
    df: DataFrame,
    datasetName: String = "Unknown Dataset"
  ): QualityReport = {
    
    println(s"🔍 开始数据质量评估: $datasetName")
    println(s"📊 待检查记录数: ${df.count()}")
    println(s"📋 质量规则数: ${rules.length}")
    
    val startTime = System.currentTimeMillis()
    
    // 顺序调用所有质量规则;各规则内部的Spark作业由Spark执行
    val results = rules.zipWithIndex.map { case (rule, index) =>
      println(s"  ⏳ [${index + 1}/${rules.length}] 执行规则: ${rule.name}")
      val result = rule.validate(df)
      val status = if (result.passed) "✅" else "❌"
      println(s"    $status ${rule.category}: ${result.message}")
      result
    }.toList
    
    val endTime = System.currentTimeMillis()
    val duration = (endTime - startTime) / 1000.0
    
    // 生成综合报告
    val report = QualityReport(
      datasetName = datasetName,
      timestamp = LocalDateTime.now(),
      totalRules = rules.length,
      totalRecords = df.count(),
      results = results,
      executionTimeSeconds = duration,
      overallScore = calculateOverallScore(results)
    )
    
    // 保存到历史记录
    history += report
    
    report
  }
  
  // 计算综合质量分数
  private def calculateOverallScore(results: List[QualityResult]): Double = {
    if (results.isEmpty) 0.0
    else {
      val weightedScores = results.map { result =>
        val weight = result.category match {
          case "完整性" => 0.3
          case "唯一性" => 0.25
          case "有效性" => 0.25
          case "一致性" => 0.2
          case _ => 0.1
        }
        result.passRate * weight
      }
      weightedScores.sum / results.groupBy(_.category).size
    }
  }
  
  // 自动修复数据问题
  def autoRepairData(df: DataFrame): (DataFrame, List[String]) = {
    var repairedDF = df
    val repairActions = ListBuffer[String]()
    
    rules.foreach { rule =>
      rule.autoFix(repairedDF) match {
        case Some(fixedDF) =>
          repairedDF = fixedDF
          repairActions += s"应用自动修复: ${rule.name}"
        case None =>
          repairActions += s"无法自动修复: ${rule.name}"
      }
    }
    
    (repairedDF, repairActions.toList)
  }
  
  // 生成质量趋势分析
  def analyzeQualityTrend(period: Int = 10): TrendAnalysis = {
    val recentReports = history.takeRight(period)
    
    if (recentReports.length < 2) {
      TrendAnalysis("数据不足", 0.0, List.empty)
    } else {
      val scores = recentReports.map(_.overallScore)
      val trend = scores.last - scores.head
      val trendDescription = if (trend > 0.05) "显著改善"
                           else if (trend > 0.01) "轻微改善"
                           else if (trend < -0.05) "显著恶化"
                           else if (trend < -0.01) "轻微恶化"
                           else "保持稳定"
      
      TrendAnalysis(
        trendDescription,
        trend,
        recentReports.map(r => (r.timestamp.toString, r.overallScore)).toList
      )
    }
  }
}

// 质量报告数据结构
case class QualityReport(
  datasetName: String,
  timestamp: LocalDateTime,
  totalRules: Int,
  totalRecords: Long,
  results: List[QualityResult],
  executionTimeSeconds: Double,
  overallScore: Double
) {
  
  def passedRules: Int = results.count(_.passed)
  def failedRules: Int = results.count(!_.passed)
  def highSeverityIssues: Int = results.count(_.severity == "HIGH")
  def mediumSeverityIssues: Int = results.count(_.severity == "MEDIUM")
  
  def printSummary(): Unit = {
    println(s"""
    |📋 数据质量评估报告
    |====================
    |📊 数据集: $datasetName
    |⏰ 评估时间: $timestamp
    |📏 数据记录: $totalRecords 条
    |🔍 检查规则: $totalRules 个
    |✅ 通过规则: $passedRules 个
    |❌ 失败规则: $failedRules 个
    |🔴 高危问题: $highSeverityIssues 个
    |🟡 中危问题: $mediumSeverityIssues 个
    |⭐ 综合得分: ${(overallScore * 100).formatted("%.2f")} 分
    |⏱️ 执行用时: ${executionTimeSeconds.formatted("%.2f")} 秒
    |
    |📋 问题详情:
    |${results.filter(!_.passed).map(r => 
      s"  • ${r.category} - ${r.ruleName}: ${r.message}"
    ).mkString("\n")}
    |
    |💡 改进建议:
    |${results.filter(!_.passed).flatMap(_.recommendations).distinct.map(r => 
      s"  • $r"
    ).mkString("\n")}
    """.stripMargin)
  }
  
  def isHealthy(threshold: Double = 0.8): Boolean = overallScore >= threshold
  
  def getWorstCategories(top: Int = 3): List[(String, Double)] = {
    results.groupBy(_.category)
      .mapValues(_.map(_.passRate).sum / _.length)
      .toList
      .sortBy(_._2)
      .take(top)
  }
}

case class TrendAnalysis(
  description: String,
  trendValue: Double,
  historicalData: List[(String, Double)]
)

example 📊 使用示例:电商订单数据质量检查

// 创建质量监控引擎
val qualityEngine = new DataQualityEngine(spark)

// 添加质量规则
qualityEngine
  .addRule(new CompletenessRule("order_id", 1.0))
  .addRule(new CompletenessRule("customer_id", 0.95))
  .addRule(new UniquenessRule("order_id"))
  .addRule(new ValidityRule("amount", col("amount") > 0))
  .addRule(new ValidityRule("email", col("email").rlike("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")))
  .addRule(new ConsistencyRule(
    "Order_Customer_Consistency",
    "检查订单与客户关系的一致性",
    df => {
      val violations = df.join(customerDF, Seq("customer_id"), "left")
        .filter(col("customer_name").isNull).count()
      (violations, df.count())
    }
  ))

// 执行质量评估
val report = qualityEngine.runQualityAssessment(orderDF, "电商订单数据")
report.printSummary()

// 自动修复数据问题
val (repairedData, repairActions) = qualityEngine.autoRepairData(orderDF)
println(s"修复操作: ${repairActions.mkString(", ")}")

// 质量趋势分析
val trend = qualityEngine.analyzeQualityTrend()
println(s"质量趋势: ${trend.description}")

🔗 学习连接

📖 本节要点回顾

  • 企业ETL管道:数据提取、转换、装载的完整流程
  • 实时流处理:高并发数据流的实时分析和处理
  • 数据质量监控:全方位的数据质量检查和自动修复
  • 性能优化:Spark应用的调优策略和最佳实践

🎯 下一步学习路径


success 🎉 恭喜你完成数据开发实战!

🏗️ 本节涉及的实践方向(掌握程度需通过运行、测试和项目实践检验):

  • 📊 企业级ETL设计 - 构建可靠的数据管道
  • ⚡ 实时流处理 - 处理高并发数据流
  • 🔍 数据质量管控 - 确保数据可信度
  • 🚀 性能优化技巧 - 让系统高效运行

💡 实践建议:理论知识需要结合实际项目来深化理解。建议从小规模数据开始练习,逐步增加复杂度!