spark mapWithState 实现

本文介绍Spark Streaming中的mapWithState()函数,此函数允许保存流的状态,用于当前和历史数据的比较及聚合。通过创建MapWithStateRDD,实现了实时数据与历史状态的交互。

mapWithState()可以保存流的状态,并能做到当前rdd和前一段时间的rdd进行比较或者聚合。

当stream调用mapWithState()方法的时候,将会返回一个MapWithStateDStreamImpl。

@Experimental
def mapWithState[StateType: ClassTag, MappedType: ClassTag](
    spec: StateSpec[K, V, StateType, MappedType]
  ): MapWithStateDStream[K, V, StateType, MappedType] = {
  new MapWithStateDStreamImpl[K, V, StateType, MappedType](
    self,
    spec.asInstanceOf[StateSpecImpl[K, V, StateType, MappedType]]
  )
}

override val mustCheckpoint = true

/** Override the default checkpoint duration */
override def initialize(time: Time): Unit = {
  if (checkpointDuration == null) {
    checkpointDuration = slideDuration * DEFAULT_CHECKPOINT_DURATION_MULTIPLIER
  }
  super.initialize(time)
}

private val internalStream =
  new InternalMapWithStateDStream[KeyType, ValueType, StateType, MappedType](dataStream, spec)
override def compute(validTime: Time): Option[RDD[MappedType]] = {
  internalStream.getOrCompute(validTime).map { _.flatMap[MappedType] { _.mappedData } }
}

其强制进行checkpoint,并规定了checkpoint的时间间隔。

当其compute()方法被调用的时候,真正经过调用的是其内部类InternalMapWithStateDStream的compute()方法。

override def compute(validTime: Time): Option[RDD[MapWithStateRDDRecord[K, S, E]]] = {
  // Get the previous state or create a new empty state RDD
  val prevStateRDD = getOrCompute(validTime - slideDuration) match {
    case Some(rdd) =>
      if (rdd.partitioner != Some(partitioner)) {
        // If the RDD is not partitioned the right way, let us repartition it using the
        // partition index as the key. This is to ensure that state RDD is always partitioned
        // before creating another state RDD using it
        MapWithStateRDD.createFromRDD[K, V, S, E](
          rdd.flatMap { _.stateMap.getAll() }, partitioner, validTime)
      } else {
        rdd
      }
    case None =>
      MapWithStateRDD.createFromPairRDD[K, V, S, E](
        spec.getInitialStateRDD().getOrElse(new EmptyRDD[(K, S)](ssc.sparkContext)),
        partitioner,
        validTime
      )
  }


  // Compute the new state RDD with previous state RDD and partitioned data RDD
  // Even if there is no data RDD, use an empty one to create a new state RDD
  val dataRDD = parent.getOrCompute(validTime).getOrElse {
    context.sparkContext.emptyRDD[(K, V)]
  }
  val partitionedDataRDD = dataRDD.partitionBy(partitioner)
  val timeoutThresholdTime = spec.getTimeoutInterval().map { interval =>
    (validTime - interval).milliseconds
  }
  Some(new MapWithStateRDD(
    prevStateRDD, partitionedDataRDD, mappingFunction, validTime, timeoutThresholdTime))
}

首先,获取上一个时间窗口的rdd,如果没有,则新建一个空的MapWithStateRDD作为下一个时间窗口可以访问到的前置rdd。如果取得到,那么判断前置rdd是否和当前分区一致,如果一致,则直接获得,否则也是以前置rdd为基础新建一个当前分区情况的MapWithStateRDD。

之后获取当前的实时rdd并分区,将该rdd和在方法之初获得的前置rdd为参数,构造新的MapWithStateRDD。

 

最后看到这个MapWithStateRDD的compue()方法的实现。

override def compute(
    partition: Partition, context: TaskContext): Iterator[MapWithStateRDDRecord[K, S, E]] = {

  val stateRDDPartition = partition.asInstanceOf[MapWithStateRDDPartition]
  val prevStateRDDIterator = prevStateRDD.iterator(
    stateRDDPartition.previousSessionRDDPartition, context)
  val dataIterator = partitionedDataRDD.iterator(
    stateRDDPartition.partitionedDataRDDPartition, context)

  val prevRecord = if (prevStateRDDIterator.hasNext) Some(prevStateRDDIterator.next()) else None
  val newRecord = MapWithStateRDDRecord.updateRecordWithData(
    prevRecord,
    dataIterator,
    mappingFunction,
    batchTime,
    timeoutThresholdTime,
    removeTimedoutData = doFullScan // remove timed-out data only when full scan is enabled
  )
  Iterator(newRecord)
}

根据分区获得前置rdd和当前rdd的分区的数据,调用MapWithStateRDDRecord的updateRecordWithData()方法根据用户所定义的方法去更新新的rdd。

def updateRecordWithData[K: ClassTag, V: ClassTag, S: ClassTag, E: ClassTag](
  prevRecord: Option[MapWithStateRDDRecord[K, S, E]],
  dataIterator: Iterator[(K, V)],
  mappingFunction: (Time, K, Option[V], State[S]) => Option[E],
  batchTime: Time,
  timeoutThresholdTime: Option[Long],
  removeTimedoutData: Boolean
): MapWithStateRDDRecord[K, S, E] = {
  // Create a new state map by cloning the previous one (if it exists) or by creating an empty one
  val newStateMap = prevRecord.map { _.stateMap.copy() }. getOrElse { new EmptyStateMap[K, S]() }

  val mappedData = new ArrayBuffer[E]
  val wrappedState = new StateImpl[S]()

  // Call the mapping function on each record in the data iterator, and accordingly
  // update the states touched, and collect the data returned by the mapping function
  dataIterator.foreach { case (key, value) =>
    wrappedState.wrap(newStateMap.get(key))
    val returned = mappingFunction(batchTime, key, Some(value), wrappedState)
    if (wrappedState.isRemoved) {
      newStateMap.remove(key)
    } else if (wrappedState.isUpdated
        || (wrappedState.exists && timeoutThresholdTime.isDefined)) {
      newStateMap.put(key, wrappedState.get(), batchTime.milliseconds)
    }
    mappedData ++= returned
  }

  // Get the timed out state records, call the mapping function on each and collect the
  // data returned
  if (removeTimedoutData && timeoutThresholdTime.isDefined) {
    newStateMap.getByTime(timeoutThresholdTime.get).foreach { case (key, state, _) =>
      wrappedState.wrapTimingOutState(state)
      val returned = mappingFunction(batchTime, key, None, wrappedState)
      mappedData ++= returned
      newStateMap.remove(key)
    }
  }

  MapWithStateRDDRecord(newStateMap, mappedData)
}

首先获取前置rdd的所有数据,并遍历当前的rdd的key,如果能够在前置的rdd中获取得到相应的key,那么就获取之前rdd的键值对调用用户定义的mappingFunction执行用户所定义的逻辑。

根据用户方法返回的结果执行更新或者移出操作,并返回,达成实时rdd与历史rdd比较归并的目的。

### Spark期末考试复习资料总结 Spark作为大数据处理的重要工具,其期末考试的复习重点通常围绕核心概念、编程模型、架构设计以及实际应用展开。以下是关于Spark期末考试复习的重点内容: #### 1. Spark的核心概念 - **RDD(Resilient Distributed Dataset)**:这是Spark的基本数据抽象,理解其不可变性、惰性计算以及分区机制是关键[^3]。 - **Transformation与Action**:掌握常见的Transformation操作(如`map`、`filter`、`reduceByKey`等)和Action操作(如`collect`、`count`、`saveAsTextFile`等)[^4]。 - **宽依赖与窄依赖**:了解不同依赖类型对任务调度和性能的影响[^5]。 #### 2. Spark SQL与DataFrame/Dataset API - **DataFrame与Dataset的区别**:DataFrame是一种以表格形式组织数据的结构,而Dataset则提供了类型安全的支持[^6]。 - **SQL查询优化**:熟悉Catalyst Optimizer的工作原理及其在查询优化中的作用[^7]。 - **常用API**:复习`select`、`filter`、`groupBy`、`join`等操作,并结合具体场景进行练习[^8]。 #### 3. Spark Streaming - **DStream与微批处理**:理解Spark Streaming基于微批处理的模型及其工作流程[^9]。 - **窗口操作**:掌握窗口函数(如`window`、`slide`)的应用场景及实现方式[^10]。 - **状态管理**:学习如何使用`updateStateByKey`或`mapWithState`来维护流处理中的状态信息[^11]。 #### 4. Spark架构与部署 - **Spark运行模式**:熟悉本地模式、Standalone模式、YARN模式以及Mesos模式的特点及配置方法[^12]。 - **任务调度机制**:了解Stage划分、Task分配以及Shuffle过程中的数据传输机制[^13]。 - **调优技巧**:复习内存管理、并行度设置、广播变量及累加器的使用等性能优化手段[^14]。 #### 5. 实际案例分析 - **常见问题解决**:针对OOM(Out of Memory)、Shuffle性能瓶颈等问题,复习对应的解决方案[^15]。 - **代码调试技巧**:掌握日志分析、任务监控以及错误排查的方法[^16]。 ```python # 示例代码:RDD Transformation与Action rdd = sc.parallelize([1, 2, 3, 4, 5]) squared_rdd = rdd.map(lambda x: x * x) result = squared_rdd.collect() print(result) # 输出 [1, 4, 9, 16, 25] ``` ```sql -- 示例代码:Spark SQL查询 SELECT department, COUNT(*) AS employee_count FROM employees GROUP BY department ORDER BY employee_count DESC; ``` #### 6. 复习建议 - **教材与PPT**:根据任课老师的推荐,仔细阅读上课PPT及相关书籍,确保对基础概念的理解深入[^17]。 - **课后习题**:重点关注每章的课后习题,尤其是涉及代码实现的部分,这些题目往往直接反映考试重点[^18]。 - **模拟测试**:通过完成历年真题或模拟试卷,检验自己的复习效果,并及时查漏补缺[^19]。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值