Spark应用(一)提取RDD内部信息和信息放入RDD

本文介绍如何从Apache Spark的RDD中提取信息,特别是针对分布式环境下的数据获取方式,并探讨使用累加器和闭包来实现这一目标的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提取RDD内部信息
     对于提取RDD内部信息有的人或许认为是一件非常简单的事情,“直接foreach操作不就行了?”。如果RDD不是为了分布式式计算。那么這样的逻辑就完全可以。
直接在外部顶一个可变量A,然后遍历RDD,A放入RDD遍历逻辑中,遍历完之后,就可以提取RDD内部的信息。但是RDD是为分布式而设计,也就是说,你這样的逻辑放在一个分布式中就行不通了。下面看一个例子

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

/**
  * Created by legotime on 3/7/17.
  */
object RddInfo {
  def getInfo(sc:SparkContext,rdd:RDD[Int])={
    val br = new StringBuffer()
    rdd.foreach {
      part =>
        br.append(part)
        println("rdd运算之内:"+br)
    }
    println("rdd运算之外:"+br)
    br
  }


  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("saveAsTextFile").setMaster("local")
    val sc =new SparkContext(conf)

    val rdd = sc.makeRDD(List(2,3,4,5),2)
   print(getInfo(sc,rdd))

  }
}
结果:

rdd运算之内:2
rdd运算之内:23
rdd运算之内:4
rdd运算之内:45
rdd运算之外:

你会发现br在rdd,foreach内部还好好的,但是foreach完之后就返回没有放入之前一样,
理由:因为br是driver端的一个顶一的一个变量,而RDD遍历操作是一个executor端的操作逻辑
分析

再来看一下foreach操作函数:

你会发现在runJob的时候,它是只针对RDD进行提交的,也就是说,别看你丢进那个变量到的foreach中了,但是实际操作的时候,根本不带上它。
也就是说br还在driver端内存中,而没有出现在在各个executor(因为只有被序列化的闭包才可以发送到executor上),所以br等于没有执行foreach内部逻辑
那怎么去操作才可以拿到RDD内部的数据。

Spark中可以设置累加器来针对這样的需求
自定义累加器:
object StringAccumulatorParam extends AccumulatorParam[String] {
  override def addInPlace(r1: String, r2: String): String = add(r1,r2)

  /**
    * 初始化
    * @param initialValue 初始值
    * @return
    */
  override def zero(initialValue: String): String = ""
  def  add(v1:String,v2:String)={
    assert((!v1.isEmpty)|| (!v2.isEmpty))
    v1+v2+" "
  }
}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import txtMIning.StringAccumulatorParam

/**
  * Created by legotime on 3/7/17.
  */
object RddInfo {
  def getInfo(sc:SparkContext,rdd:RDD[Int])={
    val br = new StringBuffer()
    rdd.foreach {
      part =>
        br.append(part)
        println("rdd运算之内:"+br)
    }
    println("rdd运算之外:"+br)
    br
  }
  def getInfo2(sc:SparkContext,rdd:RDD[Int])={
    val myAccumulator = sc.accumulator[String](" ")(StringAccumulatorParam)
    rdd.foreach{ word =>
      myAccumulator.add(word.toString)
      //println("rdd运算之内:"+myAccumulator.value),你会发现在这里累加器调用会报错
    }
    println("rdd运算之外:"+myAccumulator.value)
    myAccumulator.value
  }



  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("saveAsTextFile").setMaster("local")
    val sc =new SparkContext(conf)

    val rdd = sc.makeRDD(List(2,3,4,5),2)
   //print(getInfo(sc,rdd))
   print(getInfo2(sc,rdd))

  }
}
结果
2 3  4 5
信息放入RDD
     直接看例子
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

/**
  * Created by root on 3/7/17.
  */
class myConstants {
  val a = 2
  //静态闭包
  //比如
  def closures2RDD(sc:SparkContext,rdd:RDD[Int])={
    rdd.map(part => part+a).reduce(_+_)
  }

}
运行:

 println( (new myConstants).closures2RDD(sc,rdd))

报错:


解决方法
1、把类成员变量copy一份到闭包中,改为如下:
class myConstants {
  val a = 2
  //静态闭包
  //比如
  def closures2RDD(sc:SparkContext,rdd:RDD[Int])={
    val b = a
    rdd.map(part => part+b).reduce(_+_)
  }
2、把类进行序列化,改为如下:

class myConstants extends Serializable{
  val a = 2
  //静态闭包
  //比如
  def closures2RDD(sc:SparkContext,rdd:RDD[Int])={
    rdd.map(part => part+a).reduce(_+_)
  }

}
理由:再来看foreach或map這个函数(发现大多数算子都有這个清理闭包這一步)


再点进clean這个函数:

/**
* Clean a closure to make it ready to serialized and send to tasks
* (removes unreferenced variables in $outer's, updates REPL variables)
* If <tt>checkSerializable</tt> is set, <tt>clean</tt> will also proactively
* check to see if <tt>f</tt> is serializable and throw a <tt>SparkException</tt>
* if not.
*
* @param f the closure to clean
* @param checkSerializable whether or not to immediately check <tt>f</tt> for serializability
* @throws SparkException if <tt>checkSerializable</tt> is set but <tt>f</tt> is not
*  serializable
*/
private[spark] def clean[F <: AnyRef](f: F, checkSerializable: Boolean = true): F = {
  ClosureCleaner.clean(f, checkSerializable)
  f
}
一位sparkcommiter给出的理由[1]

When Scala constructs a closure, it determines which outer variables the closure will use and stores references to them in the closure object. This allows the closure to work properly even when it's called from a different scope than it was created in.
Scala sometimes errs on the side of capturing too many outer variables (see SI-1419). That's harmless in most cases, because the extra captured variables simply don't get used (though this prevents them from getting GC'd). But it poses a problem for Spark, which has to send closures across the network so they can be run on slaves. When a closure contains unnecessary references, it wastes network bandwidth. More importantly, some of the references may point to non-serializable objects, and Spark will fail to serialize the closure.
To work around this bug in Scala, the ClosureCleaner traverses the object at runtime and prunes the unnecessary references. Since it does this at runtime, it can be more accurate than the Scala compiler can. Spark can then safely serialize the cleaned closure.


1、https://www.quora.com/What-does-Closure-cleaner-func-mean-in-Spark





<think>好的,我现在需要解决用户的问题,将JavaRDD<Row>转换为JavaRDD<Map<String, Object>>。首先,我得理解用户的需求。用户可能是在使用Spark处理数据时,遇到了需要将Row对象转换为更灵活的Map结构的情况。比如,Row的结构可能比较复杂,或者用户需要动态访问字段对应的值,这时候Map会更方便操作。 接下来,我需要回忆SparkRDDRow的相关知识。Row对象在Spark SQL中通常用来表示DataFrame中的行数据。每个Row对象可以通过字段名或索引来访问数据。而JavaRDD<Map<String, Object>>则是RDD,其中每个元素都是个Map,键是字段名,值是对应的数据。 用户的问题是如何进行这种转换。我需要考虑在Java中如何操作Row对象,提取字段对应的值,然后构造Map。可能的步骤包括: 1. 获取Row的字段名列表。在DataFrame中,可以通过df.schema().fieldNames()来获取所有字段名。 2. 遍历每个Row,将每个字段的值存入Map中。这需要处理Row中每个字段的值类型,确保正确转换为Object类型。 3. 使用RDD的map操作进行转换,将每个Row转换为Map<String, Object>。 需要注意的是,SparkRDD转换操作需要在集群上序列化分发,所以要确保转换过程中使用的函数是可序列化的。例如,在Java中,使用匿名内部类可能会导致序列化问题,这时候应该使用lambda表达式或静态内部类来避免。 另外,Row对象的get方法有不同的重载,比如按索引或字段名获取值。使用字段名更直观,但需要确保字段名存在,否则会抛出异常。此外,Row中的值可能为null,需要处理这种情况,避免NullPointerException。 现在,我需要参考用户提供的引用内容,特别是引用[3],其中提到将HashMap转换为RDD,但用户的问题相反,是将RDD<Row>转为RDD<Map>。不过引用[3]中的思路可能有帮助,即利用Spark的分布式处理能力进行转换。 可能的示例代码结构: - 首先获取DataFrame的schema,得到字段名数组。 - 对JavaRDD<Row>使用map操作,对每个Row进行处理。 - 在map函数中,遍历所有字段名,从Row中取出对应的值,放入Map中。 - 收集结果得到JavaRDD<Map<String, Object>>。 还需要注意JavaScala之间的互操作性,因为Spark本身是用Scala写的,但用户使用的是Java API。例如,JavaRDDScala RDD之间的转换可能需要隐式转换,但Java API应该已经处理了这些细节。 另外,用户提到了JavaRDD,所以示例代码应该用Java编写,而不是Scala。需要确保代码符合Java语法,比如使用匿名类或lambda表达式。 可能的代码示例: ```java import org.apache.spark.api.java.JavaRDD; import org.apache.spark.sql.Row; import org.apache.spark.sql.DataFrame; // 假设已经存在个DataFrame dataDF String[] columns = dataDF.columns(); JavaRDD<Row> rowRDD = dataDF.javaRDD(); JavaRDD<Map<String, Object>> mapRDD = rowRDD.map(row -> { Map<String, Object> record = new HashMap<>(); for (String column : columns) { Object value = row.getAs(column); record.put(column, value); } return record; }); ``` 这,通过dataDF.columns()获取所有字段名,然后在map操作中遍历每个字段名,使用Row的getAs方法获取对应的值。这个方法会返回Object类型的值,适合放入Map中。 需要注意的问题: 1. 序列化问题:如果在map函数中使用外部变量(比如columns数组),需要确保这些变量是可序列化的。不过columns本身是字符串数组,应该是可序列化的。 2. 性能问题:频繁创建HashMap可能会影响性能,但在大多数情况下,这种开销是可以接受的。 3. 类型安全:从Row中获取的值是Object类型,需要后续处理时确保类型正确,否则可能导致运行时错误。 用户提供的引用[4]提到序列化错误,可能是因为在闭包中引用了不可序列化的对象。例如,如果在map函数中使用了个未实现Serializable接口的类实例,就会报错。但在这个示例中,columns是字符串数组,属于可序列化类型,应该没有问题。 总结步骤: 1. 获取DataFrame的字段名列表。 2. 对JavaRDD<Row>应用map转换,逐个处理每个Row。 3. 在每个Row的处理中,遍历字段名,将字段对应的值存入新的Map。 4. 返回转换后的JavaRDD<Map<String, Object>>。 这样用户就可以得到所需的RDD结构了。同时,要提醒用户注意处理可能的null值,以及确保字段名的正确性,避免运行时错误。</think>在 Spark 中将 $JavaRDD<Row>$ 转换为 $JavaRDD<Map<String, Object>>$ 可以通过以下步骤实现: ### 实现方法 1. **获取 Schema 字段名**:通过 DataFrame 的 `columns()` 方法获取字段名数组 2. **使用 map 转换**:遍历每个 $Row$ 对象,将字段名与对应值存入 $Map$ 3. **处理数据类型**:通过 $Row.getAs()$ 方法自动转换数据类型为 $Object$ ```java import org.apache.spark.sql.*; import org.apache.spark.api.java.JavaRDD; import java.util.HashMap; import java.util.Map; // 示例代码 SparkSession spark = SparkSession.builder().appName("RowToMapDemo").getOrCreate(); // 创建示例 DataFrame Dataset<Row> dataDF = spark.read().csv("hdfs://path/to/data.csv"); JavaRDD<Row> rowRDD = dataDF.javaRDD(); // 获取字段名集合 String[] columns = dataDF.columns(); // 核心转换逻辑 JavaRDD<Map<String, Object>> mapRDD = rowRDD.map(row -> { Map<String, Object> record = new HashMap<>(); for (String column : columns) { // 注意处理 null 值 Object value = row.isNullAt(row.fieldIndex(column)) ? null : row.getAs(column); record.put(column, value); } return record; }); // 验证结果 mapRDD.take(3).forEach(System.out::println); ``` ### 注意事项 1. **序列化问题**:确保闭包中使用的对象可序列化(如示例中的 $columns$ 数组是字符串数组,已满足要求)[^4] 2. **空值处理**:建议使用 $row.isNullAt()$ 判断空值,避免直接调用 $getAs()$ 可能引发的空指针异常 3. **类型转换**:$getAs()$ 方法会根据 Schema 自动转换数据类型为对应的 Java 对象(如 String、Integer 等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值