把一个dataset的表放在另一个dataset里面_SparkRDD转DataSet/DataFrame的一个深坑

b150ee1f368fef122adf6c7e324040dc.png

大数据技术与架构 点击右侧关注,大数据开发领域最强公众号! 9db5f640df6e5c86e418b49537042510.png

13a56193eebcbb55441f8a393439e94a.png

暴走大数据 点击右侧关注,暴走大数据! 5602cc3e21061f4539d2d854e61edeb5.png By  大数据技术与架构 场景描述:本文是根据读者反馈的一个问题总结而成的。 关键词:Saprk RDD原需求:希望在map函数中将每一个rdd转为DataSet或者DataFrame。  

SparkRDD转为DataSet的两种方式

第一种方法是使用反射来推断包含特定对象类型的RDD的模式。在写Spark程序的同时,已经知道了模式,这种基于反射的方法可以使代码更简洁并且程序工作得更好。第二种方法是通过一个编程接口来实现,这个接口允许构造一个模式,然后在存在的RDD上使用它。虽然这种方法代码较为冗长,但是它允许在运行期间之前不知道列以及列的类型的情况下构造DataSet。 官方给出的两个案例:
  • 利用反射推断Schema

Spark SQL支持将javabean的RDD自动转换为DataFrame。使用反射获得的BeanInfo定义了表的模式。目前,Spark SQL不支持包含Map字段的javabean。但是支持嵌套的javabean和列表或数组字段。您可以创建一个实现Serializable的类并为其所有字段设置getter和setter,从而创建一个JavaBean。
  public static class Person implements Serializable {    private String name;    private int age;    public String getName() {      return name;    }    public void setName(String name) {      this.name = name;    }    public int getAge() {      return age;    }    public void setAge(int age) {      this.age = age;    }  }
  private static void runInferSchemaExample(SparkSession spark) {    // Create an RDD of Person objects from a text file    JavaRDD peopleRDD = spark.read()      .textFile("examples/src/main/resources/people.txt")      .javaRDD()      .map(line -> {        String[] parts = line.split(",");        Person person = new Person();        person.setName(parts[0]);        person.setAge(Integer.parseInt(parts[1].trim()));        return person;      });    // Apply a schema to an RDD of JavaBeans to get a DataFrame    Dataset peopleDF = spark.createDataFrame(peopleRDD, Person.class);    // Register the DataFrame as a temporary view    peopleDF.createOrReplaceTempView("people");    // SQL statements can be run by using the sql methods provided by spark    Dataset teenagersDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19");    // The columns of a row in the result can be accessed by field index    Encoder<String> stringEncoder = Encoders.STRING();    Dataset<String> teenagerNamesByIndexDF = teenagersDF.map(        (MapFunctionString>) row -> "Name: " + row.getString(0),        stringEncoder);    teenagerNamesByIndexDF.show();    // +------------+    // |       value|    // +------------+    // |Name: Justin|    // +------------+    // or by field name    Dataset<String> teenagerNamesByFieldDF = teenagersDF.map(        (MapFunctionString>) row -> "Name: " + row.<String>getAs("name"),        stringEncoder);    teenagerNamesByFieldDF.show();    // +------------+    // |       value|    // +------------+    // |Name: Justin|    // +------------+    // $example off:schema_inferring$  }
  • 编程指定Schema

如果不能提前定义JavaBean类(例如,记录的结构是在字符串中编码的,或者将对文本数据集进行解析,而对不同的用户将对字段进行不同的投影),那么可以通过三个步骤以编程方式创建DataSet。
  private static void runProgrammaticSchemaExample(SparkSession spark) {    // 1、创建一个RDD    JavaRDD<String> peopleRDD = spark.sparkContext()      .textFile("examples/src/main/resources/people.txt", 1)      .toJavaRDD();    // The schema is encoded in a string    String schemaString = "name age";    // 2、根据schema的字符串生成schema    List fields = new ArrayList<>();    for (String fieldName : schemaString.split(" ")) {      StructField field = DataTypes.createStructField(fieldName, DataTypes.StringType, true);      fields.add(field);    }    StructType schema = DataTypes.createStructType(fields);    // 3、将JavaRDD的记录转换成JavaRDD    JavaRDD rowRDD = peopleRDD.map((Function<String, Row>) record -> {      String[] attributes = record.split(",");      return RowFactory.create(attributes[0], attributes[1].trim());    });    ///4、将 schema 应用在JavaRDD ,创建 Dataset    Dataset peopleDataFrame = spark.createDataFrame(rowRDD, schema);    // Creates a temporary view using the DataFrame    peopleDataFrame.createOrReplaceTempView("people");    // SQL can be run over a temporary view created using DataFrames    Dataset results = spark.sql("SELECT name FROM people");    // The results of SQL queries are DataFrames and support all the normal RDD operations    // The columns of a row in the result can be accessed by field index or by field name    Dataset<String> namesDS = results.map(        (MapFunctionString>) row -> "Name: " + row.getString(0),        Encoders.STRING());    namesDS.show();    // +-------------+    // |        value|    // +-------------+    // |Name: Michael|    // |   Name: Andy|    // | Name: Justin|    // +-------------+    // $example off:programmatic_schema$  }
 
Task not serializable
作者的代码类似在map中使用了方法传入的SparkContext/SparkSession,伪代码如下:
source.map(rdd->sparkSession.createDataFrame)
报了如下的错误:
org.apache.spark.SparkException: Job aborted due to stage failure: Task not serializable: java.io.NotSerializableException: ...
网上也提供很多办法,包括:
  • @Transient 注解

class MyTest1(conf:String) extends Serializable{  val list = List("a.com", "www.b.com", "a.cn", "a.com.cn", "a.org");  @transient  private val sparkConf = new SparkConf().setAppName("AppName");  @transient  private val sc = new SparkContext(sparkConf);  val rdd = sc.parallelize(list);  private val rootDomain = conf  def getResult(): Array[(String)] = {    val result = rdd.filter(item => item.contains(rootDomain))    result.take(result.count().toInt)  }}
注解是方法级别的,不是变量级别。
  • 方法实现implements Serializable

例如:
public class RDDTest implements Serializable
  • 设置一个参数

sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
 

简单的分析

以上的方法,不一定管用。 在编写Spark程序中,由于在map等算子内部使用了外部定义的变量和函数,由于外部定义的变量和函数有可能不支持序列化,仍然会导致整个类序列化时出现问题,最终可能会出现Task未序列化问题。引用了类的成员函数,会导致该类及所有成员都需要支持序列化。因此,对于使用了某类成员变量或函数的情形,首先该类需要序列化(Serializable),同时需要对某些不需要序列化的成员变量标记以避免为序列化造成影响。所以:
  • 引用了类的成员函数或变量,对应的类需要做序列化处理

  • 执行map等方法的时候,尽量不要在闭包内部直接引用成员函数或变量

如果上述办法全都不管用,那么就换个实现方案吧。 欢迎点赞+收藏+转发朋友圈素质三连

aa642c57518b6e28d6d27c9d2e6f5871.png2b1e74c4b74844d01a3775ca73d51309.png

文章不错?点个【在看】吧! ?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值