多功能JSON和YAML解析器——关于Apache Spark中大数据处理的某些方面

338 阅读4分钟

这次我演示了如何配置通用的JSON和YAML解析器以用于Spark应用程序。

一个Spark应用程序通常需要摄取JSON数据来转换数据,然后在数据源中保存数据。另一方面,YAML数据主要需要用来配置Spark作业。在这两种情况下,数据都需要根据预定义的模板进行解析。在Java Spark应用程序中,这些模板是POJO。如何对一个单一的解析器方法进行编程,以处理来自本地或分布式文件系统的数据的这类POJO模板的广泛类别?

这篇文章的组织结构如下:

  • 第1节演示了如何配置一个多功能的紧凑型JSON解析器。
  • 第2节展示了如何为YAML解析器做同样的事情。

JSON解析器。

一个Spark应用可能需要从本地文件系统或分布式Hadoop文件系统(hdfs)中读取一个JSON文件。通常在本地的情况下,配置数据被上传。另一种情况,当本地JSON数据被上传时,是在本地模式下运行、测试和调试Spark应用程序。在这些情况下,有一个JSON解析器是很好的,我们可以在本地文件系统和hdfs之间快速切换。

为了解决这个问题,让我们看看本地文件系统和hdfs的数据流有何不同。对于一个本地文件系统,可以通过以下方式获得一个DataInputStream

Java

DataInputStream dataInputStream = new DataInputStream(
        new FileInputStream("src/test/resources/file_name.json"));

另一方面,hdfs的数据流是以一种更复杂的方式获得的。

Java

final SparkSession sparkSession = SparkSession
.builder().config("//--config params--//")
.appName("APP_NAME_1")
//--enable other features--//
.getOrCreate();
SparkContext sparkContext = sparkSession.sparkContext();
final Configuration hadoopConf = sparkContext.hadoopConfiguration();
SerializableConfiguration scfg = new SerializableConfiguration(hadoopConf);
FileSystem fileSystem = FileSystem.get(scfg.value());
FSDataInputStream stream = fileSystem.open(new Path("//--hdfs file path--"));

首先,创建一个Spark会话;这个会话被配置为使用hdfs和其他分布式数据源(Apache Hive)。其次,经过4个中间步骤,获得一个分布式FileSystem 的实例。最后,创建一个分布式FSDataInputStream

本地和分布式文件流是相关的。

Java

public class FSDataInputStream extends DataInputStream implements Seekable, etc

所以,本地和分布式数据流可以在同一个解析器中使用通用的<? extends DataInputStream> 输入流类型。让我们试试下面的解决方案。

Java

public class ParserService  {
  public static  <T extends DataInputStream, B> B parseSingleDeclarationStream(T inputStream,
          Class<B> className) throws IOException {
      Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
      JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
      B message = gson.fromJson(reader, className);
      reader.close();
      return message;
  }
}

我们使用Gson库来解析一个输入流。同时,我们需要提供一个POJO类的名称,以便这个解析器能够工作。

Java

DataInputStream dataInputStream = new DataInputStream(
                new FileInputStream("src/test/resources/declaration.json"));
        Declaration declaration = ParserService.parseSingleDeclarationStream(dataInputStream, Declaration.class);
        assertEquals(declaration.getDeclaration_id(), "ABCDE12345"); 
        assertEquals(declaration.getDeclaration_details().length,1);     
       assertEquals(declaration.getDeclaration_details()[0].getDetail_1(),10);

这里的DeclarationDeclarationDetails 是简单的POJOs。

Java

public class Declaration {
    private String declaration_id;
	private Short declaration_version;
	private Short declaration_type;
	private Date declaration_date;
	private DeclarationDetails[] declaration_details;
  //--getters and setters--//
}
public class DeclarationDetails {	
	private int detail_1;
	private int detail_2;
  //--getters and setters--//
}

所以,这个解析器正确地解析了子数组和对象。请注意,Gson也可以解析日期/时间字符串,只要这些字符串符合一个模式字符串(本例中为 "yyyy-MM-dd")。

YAML解析器

在我们的项目中,我们使用本地YAML文件来指定Spark作业。例如,一个将数据从Postgres数据库传输到Hive数据库的基本Spark作业配置看起来是这样的。

YAML

---
jobName: JOB_1
hiveTable: hive_table_1
rdbTable: rdb_table_1
---
jobName: JOB_2
hiveTable: hive_table_2
rdbTable: rdb_table_2

在这里,我们需要将数据从关系型数据库的表(rdbTables)转移到相应的蜂巢表(hiveTables)。我们需要上传并解析这些配置数据,得到一个List<JobConfig> ,其中JobConfig

Java

public class JobConfig {
		private String jobName;
	    private String hiveTable;	  
	    private String select;
  //--setters and getters--//
}

下面的解析器可以解决这个问题。

Java

public class ParserService  {
  public static <T extends JobConfig> List<T> getLocalFSJobList(String path,
          Class<T> className) throws FileNotFoundException {
      List<T> list = new ArrayList<>();
      DataInputStream dataStream = new DataInputStream(
              new FileInputStream(path));
      YamlDecoder dec = new YamlDecoder(dataStream);
      YamlStream<T> stream = dec.asStreamOfType(className);
      while (stream.hasNext()) {
          T item = stream.next();
          list.add(item);
      }
      return list;
  }
}

这里我们使用jyaml库的YamlDecoder 。我们使用一个本地的DataInputStream ,尽管一个子流类,像前一部分那样,也可以使用。此外,这个解析器使用了一个<T extends JobConfig> 输出类型。这样一个更具体的输出类型允许我们在基本的JobConfig 类中添加额外的参数。

请注意,这个算法不需要扩展JobConfig的超类就能工作。我添加了这个约束,因为通常YAML文件是本地的,并且指定Spark作业,所以更多的通用类型是没有必要的。

这个解析器的运行方式如下。

Java

@Test
	public void testYmlParser() throws FileNotFoundException {
		List<JobConfig> jobs = ParserService.getLocalFSJobList("src/test/resources/jobs.yml", JobConfig.class);
		 assertEquals(jobs.size(),2);
		 assertEquals(jobs.get(0).getJobName(),"JOB_1");
		 assertEquals(jobs.get(0).getSelect(),"rdbTable_1");
		 assertEquals(jobs.get(1).getJobName(),"JOB_2");
		 assertEquals(jobs.get(1).getSelect(),"rdbTable_2");
	}

该解析器能正确识别单个文件中的多个作业,并将这些作业组装成一个列表。详情见代码

结论

在这篇文章中,我演示了如何对多功能的JSON和YAML解析器进行编程。这些分析器可以很容易地为本地和分布式文件系统配置。此外,分析器可以接受广泛的POJO模板来解析数据。希望这些技巧能对你的项目有所帮助