这次我演示了如何配置通用的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);
这里的Declaration 和DeclarationDetails 是简单的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模板来解析数据。希望这些技巧能对你的项目有所帮助